gfx-playground
cross platform rendering playgroundfeat(editor): add rotate and scale gizmo tools
| 8 files changed, 1033 insertions(+), 366 deletions(-) | |||
|---|---|---|---|
| MOD | src/editor/debug_draw.cpp | +3 | -0 |
| MOD | src/editor/editor.cpp | +1 | -0 |
| MOD | src/editor/editor.h | +43 | -9 |
| MOD | src/editor/gizmo.cpp | +923 | -42 |
| DEL | src/editor/gizmo_common.h | +0 | -291 |
| MOD | src/editor/viewport.cpp | +61 | -20 |
| MOD | src/passes/debug/debug_draw.h | +1 | -3 |
| MOD | src/renderer.cpp | +1 | -1 |
--- a/src/editor/debug_draw.cpp
+++ b/src/editor/debug_draw.cpp
@@ -30,6 +30,9 @@ void apply_debug_settings(const Editor &e, DebugDraw &draw) {
if (e.show_cat_ddgi) {
mask |= DebugDraw::category_bit(DEBUG_CAT_DDGI);
}
+ if (e.show_cat_gizmo) {
+ mask |= DebugDraw::category_bit(DEBUG_CAT_GIZMO);
+ }
draw.category_mask = mask;
}MOD · src/editor/editor.cpp +1 -0--- a/src/editor/editor.cpp
+++ b/src/editor/editor.cpp
@@ -121,6 +121,7 @@ void draw_menu_bar(Editor &editor, RenderData &rd) {
ImGui::MenuItem("Category: selection", nullptr, &editor.show_cat_selection);
ImGui::MenuItem("Category: light", nullptr, &editor.show_cat_light);
ImGui::MenuItem("Category: DDGI", nullptr, &editor.show_cat_ddgi);
+ ImGui::MenuItem("Category: gizmo", nullptr, &editor.show_cat_gizmo);
ImGui::EndMenu();
}
ImGui::EndMenu();MOD · src/editor/editor.h +43 -9--- a/src/editor/editor.h
+++ b/src/editor/editor.h
@@ -1,5 +1,6 @@
#pragma once
#include "backend/rhi.h"
+#include "core/math_rtm.h"
#include "imgui.h"
#include "passes/fg.h"
#include "scene/camera.h"
@@ -27,6 +28,12 @@ namespace editor {
constexpr ImGuiWindowFlags PANEL_FLAGS = ImGuiWindowFlags_NoCollapse;
+enum GizmoTool : int {
+ GIZMO_TOOL_MOVE = 0,
+ GIZMO_TOOL_ROTATE = 1,
+ GIZMO_TOOL_SCALE = 2,
+};
+
struct TextureViewerDesc {
rhi::Image *image = nullptr;
rhi::Sampler *sampler = nullptr;
@@ -55,6 +62,15 @@ void init_log_viewer();
void apply_style(f32 scale, ImGuiStyle &style);
+// just a throttle to apply the euler -> quat only once per frame
+// afaik translate/scale wont need something like this, because they're 1:1
+// whereas rot for euler -> quat is many:1
+struct EulerEdit {
+ int id = -1;
+ vec3 euler = vec3(0.0f);
+ quat sync;
+};
+
struct Editor {
bool dock_layout_built = false;
ImFont *font_ui = nullptr;
@@ -94,25 +110,43 @@ struct Editor {
bool show_cat_selection = true;
bool show_cat_light = false;
bool show_cat_ddgi = false;
+ bool show_cat_gizmo = true;
// shader reload
bool reload_requested_ = false;
- bool show_translate_gizmo = true;
+ GizmoTool gizmo_tool = GIZMO_TOOL_MOVE; // active tool (Blender-like)
+ bool gizmo_world = true; // true = world orientation, false = local
bool gizmo_snap = false;
- f32 gizmo_snap_step = 0.1f;
- // drag runtime (owned by update_translate_gizmo; handles only)
+ f32 gizmo_snap_step = 0.1f; // move, world units
+ f32 gizmo_snap_angle = 15.0f; // rotate, degrees
+ f32 gizmo_snap_scale = 0.1f; // scale, factor step
+
+ // drag runtime (owned by update_*_gizmo; handles only)
bool gizmo_dragging = false;
int gizmo_grab_node = -1; // node the active drag belongs to
- u8 gizmo_drag_axis = 0; // 0/1/2 = X/Y/Z axis, 3 = center free-move (handle resolved at grab)
- int gizmo_hover_axis = -1; // screen-space handle hover (-1 = none, 0..2 = axis, 3 = center)
+ u8 gizmo_drag_axis = 0; // handle id gizmo axis id
+ int gizmo_hover_axis = -1; // screen-space handle hover (-1 = none)
vec3 gizmo_grab_point = vec3(0.0f); // world-space ref point at grab
vec3 gizmo_base_local = vec3(0.0f); // node local translation at grab
vec3 gizmo_base_world = vec3(0.0f); // node world origin at grab
- vec3 gizmo_plane_n = vec3(0.0f); // view-plane normal captured at center grab
- // viewport rect cache (written by end_frame UI, read pre-execute next frame)
+ vec3 gizmo_plane_n = vec3(0.0f); // drag plane normal captured at grab
+
+ // extended drag state for rotate/scale
+ mat4 gizmo_base_world_mat = identity(); // node world matrix at grab
+ mat4 gizmo_base_local_mat = identity(); // node local matrix at grab
+ vec3 gizmo_base_scale = vec3(1.0f); // node local scale at grab
+ f32 gizmo_grab_angle = 0.0f; // rotate: angle of grab point around axis
+ f32 gizmo_grab_dist = 0.0f; // scale: radial/axial distance at grab
+ vec3 gizmo_drag_u = vec3(0.0f); // rotate: basis u, perpendicular drag axis
+ vec3 gizmo_drag_v = vec3(0.0f); // rotate: basis v, perpendicular drag axis
+ int gizmo_tool_grab = -1; // tool active at grab (cancels cross-tool drags)
+ bool gizmo_world_grab = true; // orientation at grab (rebase on toggle)
ImVec2 gizmo_view_min = ImVec2(0, 0);
ImVec2 gizmo_view_size = ImVec2(0, 0);
+ // inspector rotation edit-state (see EulerEdit above)
+ EulerEdit node_rot; // id = node index
+ EulerEdit inst_rot; // id = instance slot (base + k), single-active-instance cache
};
inline bool reload_requested(Editor &e) {
@@ -146,9 +180,9 @@ void draw_timings_window(
);
void draw_texture_viewer_window(rhi::Device &device, Editor &editor, RenderData &rd);
void draw_scene_graph_window(Scene &scene, const AssetManager &mgr, int &selected_node);
-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);
-void update_translate_gizmo(Editor &e, Scene &scene, DebugDraw &draw);
+void update_gizmo(Editor &e, Scene &scene, DebugDraw &draw);
void handle_frame_begin(
Editor &e,MOD · src/editor/gizmo.cpp +923 -42--- a/src/editor/gizmo.cpp
+++ b/src/editor/gizmo.cpp
@@ -1,6 +1,7 @@
#ifdef ENABLE_EDITOR
#include "editor.h"
-#include "gizmo_common.h"
+
+#include <cassert>
#include "imgui.h"
#include "passes/debug/debug_draw.h"
@@ -10,61 +11,794 @@
namespace editor {
-void update_translate_gizmo(Editor &e, Scene &scene, DebugDraw &draw) {
- GizmoFrame f;
- if (!gizmo_begin_frame(e, scene, f)) {
+constexpr u8 GIZMO_AXIS_X = 0;
+constexpr u8 GIZMO_AXIS_Y = 1;
+constexpr u8 GIZMO_AXIS_Z = 2;
+constexpr u8 GIZMO_FREE_AXIS = 3; // center axis
+constexpr u8 GIZMO_PLANE_YZ = 4; // normal = X, spans Y+Z
+constexpr u8 GIZMO_PLANE_XZ = 5; // normal = Y, spans X+Z
+constexpr u8 GIZMO_PLANE_XY = 6; // normal = Z, spans X+Y
+constexpr u8 GIZMO_VIEW_AXIS = 7; // outer view ring (rotate)
+
+constexpr f32 GIZMO_AXIS_HIT_PX = 10.0f;
+constexpr f32 GIZMO_CENTER_RADIUS_PX = 10.0f;
+constexpr f32 GIZMO_CENTER_HIT_PX = 12.0f;
+constexpr f32 GIZMO_HANDLE_LEN_PX = 90.0f;
+constexpr f32 GIZMO_PLANE_FRAC = 0.35f;
+constexpr f32 GIZMO_RING_HIT_PX = 6.0f;
+constexpr f32 GIZMO_RING_HIT_EDGE_PX = 4.0f; // edge-on rings project to a line through center
+constexpr f32 GIZMO_OUTER_HIT_PX = 7.0f;
+constexpr f32 GIZMO_RING_DEADZONE_PX = GIZMO_CENTER_HIT_PX + 6.0f;
+constexpr f32 GIZMO_OUTER_RING_SCALE = 1.2f;
+constexpr u32 GIZMO_RING_SEGS = 48;
+
+inline vec3 gizmo_axis_dir(u8 axis) {
+ if (axis == 0) {
+ return vec3(1.0f, 0.0f, 0.0f);
+ }
+ if (axis == 1) {
+ return vec3(0.0f, 1.0f, 0.0f);
+ }
+ return vec3(0.0f, 0.0f, 1.0f);
+}
+
+inline vec4 gizmo_axis_color(u8 axis) {
+ if (axis == 0) {
+ return vec4(10.0f, 0.4f, 0.4f, 1.0f);
+ }
+ if (axis == 1) {
+ return vec4(0.4f, 10.0f, 0.4f, 1.0f);
+ }
+ return vec4(0.5f, 0.5f, 10.0f, 1.0f);
+}
+
+// planar handle -> spanned axes + normal axis
+// map map for hit/draw/grab/scale.
+struct GizmoPlaneMap {
+ u8 ia;
+ u8 ib;
+ u8 n_axis;
+};
+
+inline GizmoPlaneMap gizmo_plane_map(u8 plane_id) {
+ if (plane_id == GIZMO_PLANE_YZ) {
+ return {1, 2, GIZMO_AXIS_X};
+ }
+ if (plane_id == GIZMO_PLANE_XZ) {
+ return {0, 2, GIZMO_AXIS_Y};
+ }
+ return {0, 1, GIZMO_AXIS_Z}; // GIZMO_PLANE_XY (and invalid fallback)
+}
+
+inline u8 plane_normal_axis(u8 plane_id) {
+ return gizmo_plane_map(plane_id).n_axis;
+}
+
+// scale mask: 1 on axes affected by the handle, 0 elsewhere (0,0,0) = invalid
+inline vec3 gizmo_scale_mask(u8 handle) {
+ if (handle == GIZMO_FREE_AXIS) {
+ return vec3(1.0f);
+ }
+ if (handle == GIZMO_AXIS_X) {
+ return vec3(1.0f, 0.0f, 0.0f);
+ }
+ if (handle == GIZMO_AXIS_Y) {
+ return vec3(0.0f, 1.0f, 0.0f);
+ }
+ if (handle == GIZMO_AXIS_Z) {
+ return vec3(0.0f, 0.0f, 1.0f);
+ }
+ if (handle == GIZMO_PLANE_YZ) {
+ return vec3(0.0f, 1.0f, 1.0f);
+ }
+ if (handle == GIZMO_PLANE_XZ) {
+ return vec3(1.0f, 0.0f, 1.0f);
+ }
+ if (handle == GIZMO_PLANE_XY) {
+ return vec3(1.0f, 1.0f, 0.0f);
+ }
+ return vec3(0.0f);
+}
+
+struct GizmoSelection {
+ vec3 origin;
+ mat4 base_mat;
+};
+
+inline GizmoSelection gizmo_selection(const Scene &scene, u32 node) {
+ const SceneGraph &sg = scene.scene_graph;
+ mat4 base = (sg.instance_count[node] > 0) ? scene.instance_world(node, 0) : sg.worlds[node];
+ vec4 h = transform_point(vec3(0.0f, 0.0f, 0.0f), base);
+ return {vec3(h.x, h.y, h.z), base};
+}
+
+inline bool project_to_view(vec3 p, const mat4 &view_proj, ImVec2 min, ImVec2 size, ImVec2 &out) {
+ vec4 c = transform_point(p, view_proj);
+ if (c.w <= 0.0f) { // behind camera
+ return false;
+ }
+ f32 nx = c.x / c.w;
+ f32 ny = c.y / c.w;
+ out = ImVec2(min.x + (nx * 0.5f + 0.5f) * size.x, min.y + (ny * 0.5f + 0.5f) * size.y);
+ return true;
+}
+
+inline f32 dist_point_seg(ImVec2 p, ImVec2 a, ImVec2 b) {
+ f32 dx = b.x - a.x;
+ f32 dy = b.y - a.y;
+ f32 len2 = dx * dx + dy * dy;
+ f32 t = (len2 > 1e-8f) ? ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2 : 0.0f;
+ if (t < 0.0f) {
+ t = 0.0f;
+ } else if (t > 1.0f) {
+ t = 1.0f;
+ }
+ f32 ex = a.x + dx * t - p.x;
+ f32 ey = a.y + dy * t - p.y;
+ return sqrtf(ex * ex + ey * ey);
+}
+
+// closest point on axis (origin + s*axis, |axis|==1) to ray (ray_o + t*ray_d).
+inline vec3 closest_point_on_axis(vec3 origin, vec3 axis, vec3 ray_o, vec3 ray_d) {
+ vec3 w0 = origin - ray_o;
+ f32 b = axis.dot(ray_d);
+ f32 d = axis.dot(w0);
+ f32 f = ray_d.dot(w0);
+ f32 denom = 1.0f - b * b;
+ f32 s = (fabsf(denom) < 1e-6f) ? 0.0f : (b * f - d) / denom;
+ return origin + axis * s;
+}
+
+// signed distance along axis from origin of the closest point to the ray.
+inline f32 axis_signed_distance(vec3 origin, vec3 axis, vec3 ray_o, vec3 ray_d) {
+ vec3 p = closest_point_on_axis(origin, axis, ray_o, ray_d);
+ return (p - origin).dot(axis);
+}
+
+// ray plane intersection (point + t*n, |n|==1). Falls back to `fallback` when the ray
+// runs parallel to the plane or points away.
+inline vec3 ray_plane_point(vec3 point, vec3 n, vec3 ray_o, vec3 ray_d, vec3 fallback) {
+ f32 denom = ray_d.dot(n);
+ if (fabsf(denom) < 1e-6f) {
+ return fallback;
+ }
+ f32 t = (point - ray_o).dot(n) / denom;
+ if (!(t > 0.0f) || !std::isfinite(t)) {
+ return fallback;
+ }
+ return ray_o + ray_d * t;
+}
+
+inline vec3 gizmo_quat_rotate(vec3 v, quat q) {
+ return vec3::from_vec4(rtm::quat_mul_vector3(v.to_vec4(), (rtm::quatf)q));
+}
+
+// orthonormal basis (u,v) spanning the plane perpendicular to axis
+inline void gizmo_ring_basis(vec3 axis, vec3 &u, vec3 &v) {
+ vec3 helper = (fabsf(axis.z) < 0.9f) ? vec3(0.0f, 0.0f, 1.0f) : vec3(0.0f, 1.0f, 0.0f);
+ u = cross(helper, axis);
+ f32 l = u.length();
+ if (!(l > 1e-6f)) {
+ u = vec3(1.0f, 0.0f, 0.0f);
+ } else {
+ u = u / l;
+ }
+ v = cross(axis, u);
+ l = v.length();
+ if (l > 1e-6f) {
+ v = v / l;
+ }
+}
+
+inline f32 gizmo_wrap_pi(f32 a) {
+ while (a > PI) {
+ a -= 2.0f * PI;
+ }
+ while (a < -PI) {
+ a += 2.0f * PI;
+ }
+ return a;
+}
+
+inline f32 gizmo_angle_on_plane(vec3 p, vec3 origin, vec3 u, vec3 v) {
+ vec3 d = p - origin;
+ return atan2f(d.dot(v), d.dot(u));
+}
+
+// per-frame gizmo context, computed once per tool update.
+struct GizmoFrame {
+ u32 node = 0;
+ SceneGraph *sg = nullptr;
+ const Camera *cam = nullptr;
+ vec3 origin = vec3(0.0f);
+ mat4 base_mat = identity();
+ vec3 axes[3] = {vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)};
+ f32 dist = 0.0f;
+ f32 len = 0.0f; // screen-constant handle length (world units)
+ ImVec2 mouse = ImVec2(0, 0);
+ bool hover_view = false;
+ ImVec2 o_px = ImVec2(0, 0);
+ bool o_visible = false;
+ DebugRay ray = {};
+};
+
+inline void gizmo_compute_axes(const Editor &e, const Scene &scene, u32 node, const mat4 &base_mat, vec3 *axes) {
+ if (e.gizmo_world) {
+ axes[0] = vec3(1.0f, 0.0f, 0.0f);
+ axes[1] = vec3(0.0f, 1.0f, 0.0f);
+ axes[2] = vec3(0.0f, 0.0f, 1.0f);
return;
}
+ (void)scene;
+ quat q = mat4_to_trs(base_mat).rotation;
+ axes[0] = gizmo_quat_rotate(vec3(1.0f, 0.0f, 0.0f), q).normalized();
+ axes[1] = gizmo_quat_rotate(vec3(0.0f, 1.0f, 0.0f), q).normalized();
+ axes[2] = gizmo_quat_rotate(vec3(0.0f, 0.0f, 1.0f), q).normalized();
+ for (int i = 0; i < 3; ++i) {
+ if (!debug_finite_vec3(axes[i]) || axes[i].length_squared() < 1e-8f) {
+ axes[i] = gizmo_axis_dir((u8)i);
+ }
+ }
+}
+
+// shared origin/base/axes/dist/len resolution for begin-frame + rebase
+struct GizmoFrameData {
+ vec3 origin;
+ mat4 base_mat;
+ vec3 axes[3];
+ f32 dist = 0.0f;
+ f32 len = 0.0f;
+};
+
+inline bool gizmo_resolve_frame(const Editor &e, const Scene &scene, u32 node, const Camera &cam, GizmoFrameData &out) {
+ GizmoSelection sel = gizmo_selection(scene, node);
+ vec3 to_cam = cam.position - sel.origin;
+ f32 dist = to_cam.length();
+ if (!(dist > 0.0f) || !std::isfinite(dist)) {
+ return false;
+ }
+ // screen-constant handle size (see pixels_to_world).
+ f32 len = DebugDraw::pixels_to_world(dist, GIZMO_HANDLE_LEN_PX, cam.fov_y_deg, e.gizmo_view_size.y);
+ if (!(len > 0.0f) || !std::isfinite(len)) {
+ return false;
+ }
+ out.origin = sel.origin;
+ out.base_mat = sel.base_mat;
+ gizmo_compute_axes(e, scene, node, sel.base_mat, out.axes);
+ out.dist = dist;
+ out.len = len;
+ return true;
+}
+
+inline void gizmo_apply_frame_data(GizmoFrame &f, u32 node, const GizmoFrameData &d) {
+ f.node = node;
+ f.origin = d.origin;
+ f.base_mat = d.base_mat;
+ f.axes[0] = d.axes[0];
+ f.axes[1] = d.axes[1];
+ f.axes[2] = d.axes[2];
+ f.dist = d.dist;
+ f.len = d.len;
+}
+
+inline void gizmo_cancel_drag(Editor &e) {
+ e.gizmo_dragging = false;
+ e.gizmo_grab_node = -1;
+ e.gizmo_hover_axis = -1;
+}
+
+inline bool gizmo_begin_frame(Editor &e, Scene &scene, GizmoFrame &f) {
+ if (!e.show_cat_gizmo || e.selected_node < 0 || (u32)e.selected_node >= scene.scene_graph.node_count) {
+ gizmo_cancel_drag(e);
+ return false;
+ }
+ SceneGraph &sg = scene.scene_graph;
+ u32 node = (u32)e.selected_node;
+ if (!sg.is_node_alive(node)) {
+ gizmo_cancel_drag(e);
+ return false;
+ }
+ if (scene.active_camera >= scene.cameras.size()) {
+ gizmo_cancel_drag(e);
+ return false;
+ }
+ if (e.gizmo_view_size.x <= 0.0f || e.gizmo_view_size.y <= 0.0f) {
+ return false; // no viewport rect cached yet (first frame)
+ }
+ const Camera &cam = scene.cameras[scene.active_camera];
+
+ GizmoFrameData d;
+ if (!gizmo_resolve_frame(e, scene, node, cam, d)) {
+ gizmo_cancel_drag(e);
+ return false;
+ }
+
+ ImVec2 mouse = ImGui::GetMousePos();
+ bool hover_view = mouse.x >= e.gizmo_view_min.x && mouse.x <= e.gizmo_view_min.x + e.gizmo_view_size.x &&
+ mouse.y >= e.gizmo_view_min.y && mouse.y <= e.gizmo_view_min.y + e.gizmo_view_size.y;
+
+ ImVec2 o_px{};
+ bool o_visible = project_to_view(d.origin, cam.view_proj, e.gizmo_view_min, e.gizmo_view_size, o_px);
+
+ // mouse ray in world space (for grab + drag).
+ // NDC: up is y=-1.
+ // imgui pixels map straight through, no y-flip
+ f32 nx = ((mouse.x - e.gizmo_view_min.x) / e.gizmo_view_size.x) * 2.0f - 1.0f;
+ f32 ny = ((mouse.y - e.gizmo_view_min.y) / e.gizmo_view_size.y) * 2.0f - 1.0f;
+ DebugRay ray = debug_unproject(nx, ny, cam.position, cam.inv_view_proj);
+
+ f.sg = &sg;
+ f.cam = &cam;
+ gizmo_apply_frame_data(f, node, d);
+ f.mouse = mouse;
+ f.hover_view = hover_view;
+ f.o_px = o_px;
+ f.o_visible = o_visible;
+ f.ray = ray;
+ return true;
+}
+
+inline void gizmo_capture_common(Editor &e, SceneGraph &sg, u32 node, vec3 origin, const mat4 &base_mat, u8 handle) {
+ e.gizmo_dragging = true;
+ e.gizmo_grab_node = (int)node;
+ e.gizmo_drag_axis = handle;
+ e.gizmo_base_local = sg.translations[node];
+ e.gizmo_base_world = origin;
+ e.gizmo_base_world_mat = base_mat;
+ e.gizmo_base_local_mat = sg.locals[node];
+ e.gizmo_base_scale = sg.scales[node];
+}
+
+// FREE keeps the caller's plane (move/scale set it to the view plane first)
+// AXIS refines per tool (rotate -> ring plane, scale -> grab distance)
+// PLANAR always refreshes plane + distance (move ignores the distance)
+inline void gizmo_derive_grab_refs(Editor &e, const GizmoFrame &f, const Camera &cam, vec3 origin, u8 handle) {
+ if (handle == GIZMO_FREE_AXIS) {
+ e.gizmo_grab_point = ray_plane_point(origin, e.gizmo_plane_n, f.ray.origin, f.ray.dir, origin);
+ } else if (handle == GIZMO_VIEW_AXIS) {
+ vec3 n = cam.forward().normalized();
+ e.gizmo_plane_n = n;
+ gizmo_ring_basis(n, e.gizmo_drag_u, e.gizmo_drag_v);
+ vec3 p = ray_plane_point(origin, n, f.ray.origin, f.ray.dir, origin);
+ e.gizmo_grab_point = p;
+ e.gizmo_grab_angle = gizmo_angle_on_plane(p, origin, e.gizmo_drag_u, e.gizmo_drag_v);
+ } else if (handle <= GIZMO_AXIS_Z) {
+ vec3 axis = f.axes[handle];
+ e.gizmo_grab_point = closest_point_on_axis(origin, axis, f.ray.origin, f.ray.dir);
+ // rotate uses plane perpendicular to axis
+ if (e.gizmo_tool == GIZMO_TOOL_ROTATE) {
+ e.gizmo_plane_n = axis;
+ gizmo_ring_basis(axis, e.gizmo_drag_u, e.gizmo_drag_v);
+ vec3 p = ray_plane_point(origin, axis, f.ray.origin, f.ray.dir, origin);
+ e.gizmo_grab_point = p;
+ e.gizmo_grab_angle = gizmo_angle_on_plane(p, origin, e.gizmo_drag_u, e.gizmo_drag_v);
+ } else if (e.gizmo_tool == GIZMO_TOOL_SCALE) {
+ e.gizmo_grab_dist = (e.gizmo_grab_point - origin).dot(axis);
+ }
+ } else {
+ // planar handles: grab on their plane
+ GizmoPlaneMap m = gizmo_plane_map(handle);
+ e.gizmo_plane_n = f.axes[m.n_axis];
+ e.gizmo_grab_point = ray_plane_point(origin, e.gizmo_plane_n, f.ray.origin, f.ray.dir, origin);
+ e.gizmo_grab_dist = (e.gizmo_grab_point - origin).length();
+ }
+}
+
+inline void gizmo_rebase_drag(Editor &e, Scene &scene, GizmoFrame &f) {
SceneGraph &sg = *f.sg;
+ bool alive = e.selected_node >= 0 && (u32)e.selected_node < sg.node_count && sg.is_node_alive((u32)e.selected_node);
+ if (!alive || !debug_finite_vec3(f.ray.dir) || !debug_finite_vec3(f.ray.origin)) {
+ e.gizmo_dragging = false;
+ e.gizmo_grab_node = -1;
+ return;
+ }
+ u32 node = (u32)e.selected_node;
const Camera &cam = *f.cam;
+ GizmoFrameData d;
+ if (!gizmo_resolve_frame(e, scene, node, cam, d)) {
+ e.gizmo_dragging = false;
+ e.gizmo_grab_node = -1;
+ return;
+ }
+ gizmo_apply_frame_data(f, node, d);
+ e.gizmo_grab_node = e.selected_node;
+ e.gizmo_base_local = sg.translations[node];
+ e.gizmo_base_world = d.origin;
+ e.gizmo_base_world_mat = d.base_mat;
+ e.gizmo_base_local_mat = sg.locals[node];
+ e.gizmo_base_scale = sg.scales[node];
+ gizmo_derive_grab_refs(e, f, cam, d.origin, e.gizmo_drag_axis);
+}
- // screen-space handle hit test
- int hover_axis = -1;
- if (f.o_visible && f.hover_view && !e.gizmo_dragging) {
- if (gizmo_hit_center(f.mouse, f.o_px)) {
- hover_axis = GIZMO_FREE_AXIS;
- } else {
- hover_axis = gizmo_hit_axis(f.mouse, f.o_px, f, e);
+inline void gizmo_write_translation(SceneGraph &sg, u32 node, vec3 base_local, vec3 base_world, vec3 new_world) {
+ int p = sg.parent[node];
+ mat4 parent_world = (p >= 0) ? sg.worlds[p] : identity();
+ vec3 local_delta = transform_direction(new_world - base_world, inverse(parent_world));
+ vec3 new_local = base_local + local_delta;
+ if (debug_finite_vec3(new_local)) {
+ sg.set_translation(node, new_local);
+ }
+}
+
+// world-space delta rotation about the node origin.
+inline void gizmo_write_rotation(SceneGraph &sg, u32 node, const mat4 &base_world, vec3 axis_world, f32 delta) {
+ TRS base_trs = mat4_to_trs(base_world);
+ quat dq = quat::from_axis_angle(axis_world, delta); // normalizes axis internally
+ // quat qn = (dq * quat(base_trs.rotation)).normalized();
+ quat qn = (quat(base_trs.rotation) * dq).normalized();
+ TRS new_world_trs{base_trs.translation, qn, base_trs.scale};
+ mat4 new_world = trs_to_mat4(new_world_trs);
+ int p = sg.parent[node];
+ mat4 parent_world = (p >= 0) ? sg.worlds[p] : identity();
+ mat4 new_local = new_world * inverse(parent_world);
+ if (debug_finite_vec3(vec3(new_local.m[12], new_local.m[13], new_local.m[14]))) {
+ sg.set_local_matrix(node, new_local);
+ }
+}
+
+inline void gizmo_write_scale_local(SceneGraph &sg, u32 node, vec3 base_scale, u8 handle, f32 factor) {
+ if (!(factor > 0.0f) || !std::isfinite(factor)) {
+ return;
+ }
+ vec3 mask = gizmo_scale_mask(handle);
+ if (mask.length_squared() <= 0.0f) {
+ return;
+ }
+ vec3 s = {
+ mask.x > 0.5f ? base_scale.x * factor : base_scale.x,
+ mask.y > 0.5f ? base_scale.y * factor : base_scale.y,
+ mask.z > 0.5f ? base_scale.z * factor : base_scale.z
+ };
+ auto clamp_axis = [](f32 v) {
+ f32 a = fabsf(v);
+ if (!(a >= 1e-4f)) {
+ a = 1e-4f;
}
+ return (v < 0.0f ? -1.0f : 1.0f) * a;
+ };
+ s.x = clamp_axis(s.x);
+ s.y = clamp_axis(s.y);
+ s.z = clamp_axis(s.z);
+ if (debug_finite_vec3(s)) {
+ sg.set_scale(node, s);
}
- e.gizmo_hover_axis = hover_axis;
+}
+
+// world-space scale about the node origin (handles rotated nodes).
+inline void gizmo_write_scale_world(SceneGraph &sg, u32 node, const mat4 &base_world, u8 handle, f32 factor) {
+ if (!(factor > 0.0f) || !std::isfinite(factor)) {
+ return;
+ }
+ vec3 mask = gizmo_scale_mask(handle);
+ if (mask.length_squared() <= 0.0f) {
+ return;
+ }
+ vec3 f = vec3(1.0f) + mask * (factor - 1.0f);
+ mat4 s = scale(f);
+ mat4 new_world = base_world * s;
+ int p = sg.parent[node];
+ mat4 parent_world = (p >= 0) ? sg.worlds[p] : identity();
+ mat4 new_local = new_world * inverse(parent_world);
+ TRS t = mat4_to_trs(new_local);
+ if (t.scale.x < 1e-4f) {
+ t.scale.x = 1e-4f;
+ }
+ if (t.scale.y < 1e-4f) {
+ t.scale.y = 1e-4f;
+ }
+ if (t.scale.z < 1e-4f) {
+ t.scale.z = 1e-4f;
+ }
+ if (debug_finite_vec3(t.scale) && debug_finite_vec3(t.translation)) {
+ sg.set_local_matrix(node, trs_to_mat4(t));
+ }
+}
- // rebase, if selected node updated
- if (e.gizmo_dragging && e.selected_node != e.gizmo_grab_node) {
+inline bool gizmo_hit_center(ImVec2 mouse, ImVec2 o_px) {
+ f32 dx = mouse.x - o_px.x;
+ f32 dy = mouse.y - o_px.y;
+ return sqrtf(dx * dx + dy * dy) < GIZMO_CENTER_HIT_PX;
+}
+
+inline int gizmo_hit_axis(ImVec2 mouse, ImVec2 o_px, const GizmoFrame &f, const Editor &e) {
+ f32 best = GIZMO_AXIS_HIT_PX;
+ int hit = -1;
+ for (u8 a = 0; a < 3; ++a) {
+ ImVec2 t_px{};
+ if (!project_to_view(
+ f.origin + f.axes[a] * f.len, f.cam->view_proj, e.gizmo_view_min, e.gizmo_view_size, t_px
+ )) {
+ continue;
+ }
+ f32 d = dist_point_seg(mouse, o_px, t_px);
+ if (d < best) {
+ best = d;
+ hit = (int)a;
+ }
+ // fatten tip grab
+ f32 dx = mouse.x - t_px.x;
+ f32 dy = mouse.y - t_px.y;
+ if (sqrtf(dx * dx + dy * dy) < GIZMO_CENTER_HIT_PX && d < GIZMO_AXIS_HIT_PX + 4.0f) {
+ hit = (int)a;
+ best = d;
+ }
+ }
+ return hit;
+}
+
+inline bool gizmo_quad_contains(ImVec2 p, ImVec2 a, ImVec2 b, ImVec2 c, ImVec2 d) {
+ // convex quad a->b->c->d: same sign on all edge crosses
+ ImVec2 pts[4] = {a, b, c, d};
+ f32 sign = 0.0f;
+ for (int i = 0; i < 4; ++i) {
+ ImVec2 p0 = pts[i];
+ ImVec2 p1 = pts[(i + 1) & 3];
+ f32 cross = (p1.x - p0.x) * (p.y - p0.y) - (p1.y - p0.y) * (p.x - p0.x);
+ if (fabsf(cross) < 1e-6f) {
+ continue;
+ }
+ if (sign == 0.0f) {
+ sign = cross;
+ } else if ((sign > 0.0f) != (cross > 0.0f)) {
+ return false;
+ }
+ }
+ return sign != 0.0f;
+}
+
+// planar quad handle hit (move/scale): quad(origin, origin+a*q, origin+a*q+b*q, origin+b*q).
+inline int gizmo_hit_planar(ImVec2 mouse, const GizmoFrame &f, const Editor &e) {
+ f32 q = f.len * GIZMO_PLANE_FRAC;
+ const u8 ids[3] = {GIZMO_PLANE_YZ, GIZMO_PLANE_XZ, GIZMO_PLANE_XY};
+ for (u8 id : ids) {
+ GizmoPlaneMap m = gizmo_plane_map(id);
+ ImVec2 p0 = f.o_px, p1{}, p2{}, p3{};
+ if (!project_to_view(f.origin + f.axes[m.ia] * q, f.cam->view_proj, e.gizmo_view_min, e.gizmo_view_size, p1)) {
+ continue;
+ }
+ if (!project_to_view(
+ f.origin + (f.axes[m.ia] + f.axes[m.ib]) * q, f.cam->view_proj, e.gizmo_view_min, e.gizmo_view_size, p2
+ )) {
+ continue;
+ }
+ if (!project_to_view(f.origin + f.axes[m.ib] * q, f.cam->view_proj, e.gizmo_view_min, e.gizmo_view_size, p3)) {
+ continue;
+ }
+ // reject edge-on (tiny area)
+ f32 area = fabsf((p1.x - p0.x) * (p3.y - p0.y) - (p1.y - p0.y) * (p3.x - p0.x));
+ if (area < 80.0f) {
+ continue;
+ }
+ if (gizmo_quad_contains(mouse, p0, p1, p2, p3)) {
+ return id;
+ }
+ }
+ return -1;
+}
+
+inline f32 gizmo_ring_screen_dist(ImVec2 mouse, const GizmoFrame &f, const Editor &e, vec3 axis, f32 radius) {
+ vec3 u, v;
+ gizmo_ring_basis(axis.normalized(), u, v);
+ ImVec2 prev{};
+ bool has_prev = false;
+ f32 best = 1e30f;
+ for (u32 i = 0; i <= GIZMO_RING_SEGS; ++i) {
+ f32 t = (f32)i / (f32)GIZMO_RING_SEGS * 2.0f * PI;
+ vec3 p = f.origin + (u * cosf(t) + v * sinf(t)) * radius;
+ ImVec2 px{};
+ if (!project_to_view(p, f.cam->view_proj, e.gizmo_view_min, e.gizmo_view_size, px)) {
+ has_prev = false;
+ continue;
+ }
+ if (has_prev) {
+ f32 d = dist_point_seg(mouse, prev, px);
+ if (d < best) {
+ best = d;
+ }
+ }
+ prev = px;
+ has_prev = true;
+ }
+ return best;
+}
+
+inline int gizmo_hit_rotate(ImVec2 mouse, ImVec2 o_px, const GizmoFrame &f, const Editor &e) {
+ vec3 view_dir = f.cam->forward().normalized();
+ f32 dx = mouse.x - o_px.x;
+ f32 dy = mouse.y - o_px.y;
+ f32 center_d = sqrtf(dx * dx + dy * dy);
+ // dead-zone around the origin: edge-on rings collapse to lines through the
+ // center, so clicks here almost never mean a ring grab.
+ if (center_d < GIZMO_RING_DEADZONE_PX) {
+ return -1;
+ }
+ // outer view ring first (topmost in Blender)
+ f32 outer_r = f.len * GIZMO_OUTER_RING_SCALE;
+ f32 outer_px = GIZMO_HANDLE_LEN_PX * GIZMO_OUTER_RING_SCALE;
+ if (fabsf(center_d - outer_px) < GIZMO_OUTER_HIT_PX) {
+ f32 d = gizmo_ring_screen_dist(mouse, f, e, view_dir, outer_r);
+ if (d < GIZMO_OUTER_HIT_PX) {
+ return GIZMO_VIEW_AXIS;
+ }
+ }
+ int hit = -1;
+ f32 best = 1e30f;
+ for (u8 a = 0; a < 3; ++a) {
+ // face-on rings read as circles (easier target), edge-on as thin lines.
+ f32 facing = fabsf(f.axes[a].normalized().dot(view_dir));
+ f32 tol = facing > 0.5f ? GIZMO_RING_HIT_PX : GIZMO_RING_HIT_EDGE_PX;
+ f32 d = gizmo_ring_screen_dist(mouse, f, e, f.axes[a], f.len);
+ if (d < tol && d < best) {
+ best = d;
+ hit = (int)a;
+ }
+ }
+ return hit;
+}
+
+inline void gizmo_draw_circle(DebugDraw &draw, vec3 origin, vec3 u, vec3 v, f32 radius, vec4 col, f32 w) {
+ vec3 prev = origin + u * radius;
+ for (u32 i = 1; i <= GIZMO_RING_SEGS; ++i) {
+ f32 t = (f32)i / (f32)GIZMO_RING_SEGS * 2.0f * PI;
+ vec3 p = origin + (u * cosf(t) + v * sinf(t)) * radius;
+ draw.line(prev, p, col, DEBUG_CAT_GIZMO, DEBUG_DEPTH_XRAY, w);
+ prev = p;
+ }
+}
+
+inline void gizmo_draw_center_ring(DebugDraw &draw, const GizmoFrame &f, const Editor &e, bool active) {
+ vec4 col = active ? vec4(5.0f, 5.0f, 5.0f, 1.0f) : vec4(3.0f, 3.0f, 3.0f, 1.0f);
+ f32 w = active ? 4.0f : 3.0f;
+ f32 r = DebugDraw::pixels_to_world(f.dist, GIZMO_CENTER_RADIUS_PX, f.cam->fov_y_deg, e.gizmo_view_size.y);
+ if (!(r > 0.0f) || !std::isfinite(r)) {
+ return;
+ }
+ gizmo_draw_circle(draw, f.origin, f.cam->right(), f.cam->up(), r, col, w);
+}
+
+inline void gizmo_draw_ring(DebugDraw &draw, const GizmoFrame &f, u8 axis_idx, vec3 axis, bool active) {
+ vec4 col = active ? vec4(5.0f, 5.0f, 5.0f, 1.0f) : gizmo_axis_color(axis_idx);
+ f32 w = active ? 4.0f : 3.0f;
+ vec3 u, v;
+ gizmo_ring_basis(axis.normalized(), u, v);
+ gizmo_draw_circle(draw, f.origin, u, v, f.len, col, w);
+}
+
+inline void gizmo_draw_view_ring(DebugDraw &draw, const GizmoFrame &f, const Camera &cam, bool active) {
+ vec4 col = active ? vec4(5.0f, 5.0f, 5.0f, 1.0f) : vec4(3.0f, 3.0f, 3.0f, 1.0f);
+ f32 w = active ? 4.0f : 3.0f;
+ vec3 n = cam.forward().normalized();
+ vec3 u, v;
+ gizmo_ring_basis(n, u, v);
+ gizmo_draw_circle(draw, f.origin, u, v, f.len * GIZMO_OUTER_RING_SCALE, col, w);
+}
+
+inline void gizmo_draw_planar_quad(DebugDraw &draw, const GizmoFrame &f, u8 plane_id, bool active) {
+ GizmoPlaneMap m = gizmo_plane_map(plane_id);
+ vec4 col = gizmo_axis_color(m.n_axis);
+ col.w = 1.0f;
+ if (active) {
+ col = vec4(5.0f, 5.0f, 5.0f, 1.0f);
+ } else {
+ col.x *= 0.55f;
+ col.y *= 0.55f;
+ col.z *= 0.55f;
+ }
+ f32 w = active ? 4.0f : 2.5f;
+ f32 q = f.len * GIZMO_PLANE_FRAC;
+ vec3 p0 = f.origin;
+ vec3 p1 = f.origin + f.axes[m.ia] * q;
+ vec3 p2 = f.origin + (f.axes[m.ia] + f.axes[m.ib]) * q;
+ vec3 p3 = f.origin + f.axes[m.ib] * q;
+ draw.line(p0, p1, col, DEBUG_CAT_GIZMO, DEBUG_DEPTH_XRAY, w);
+ draw.line(p1, p2, col, DEBUG_CAT_GIZMO, DEBUG_DEPTH_XRAY, w);
+ draw.line(p2, p3, col, DEBUG_CAT_GIZMO, DEBUG_DEPTH_XRAY, w);
+ draw.line(p3, p0, col, DEBUG_CAT_GIZMO, DEBUG_DEPTH_XRAY, w);
+}
+
+inline void gizmo_draw_scale_cube(DebugDraw &draw, vec3 center, f32 size, vec4 col, f32 w) {
+ mat4 world = scale(vec3(size)) * translate(center);
+ draw.box_mat(world, col, DEBUG_CAT_GIZMO, DEBUG_DEPTH_XRAY, w);
+}
+
+// shared hover path for move/scale: center -> axis -> planar
+inline int gizmo_hover_move_scale(const GizmoFrame &f, const Editor &e) {
+ if (gizmo_hit_center(f.mouse, f.o_px)) {
+ return GIZMO_FREE_AXIS;
+ }
+ int hit = gizmo_hit_axis(f.mouse, f.o_px, f, e);
+ return hit >= 0 ? hit : gizmo_hit_planar(f.mouse, f, e);
+}
+
+// shared axis-line draw for move/scale; tip_fn draws the per-tool tip marker
+template <typename TipFn>
+inline void gizmo_draw_axes(DebugDraw &draw, const GizmoFrame &f, const Editor &e, int hover_axis, TipFn tip_fn) {
+ for (u8 a = 0; a < 3; ++a) {
+ bool active = (e.gizmo_dragging && a == e.gizmo_drag_axis) || (!e.gizmo_dragging && (int)a == hover_axis);
+ vec4 col = active ? vec4(5.0f, 5.0f, 5.0f, 1.0f) : gizmo_axis_color(a);
+ f32 w = active ? 4.0f : 3.0f;
+ vec3 tip = f.origin + f.axes[a] * f.len;
+ draw.line(f.origin, tip, col, DEBUG_CAT_GIZMO, DEBUG_DEPTH_XRAY, w);
+ tip_fn(draw, tip, f.len, col, w);
+ }
+}
+
+inline void gizmo_draw_planars(DebugDraw &draw, const GizmoFrame &f, const Editor &e, int hover_axis) {
+ for (u8 p = GIZMO_PLANE_YZ; p <= GIZMO_PLANE_XY; ++p) {
+ bool active = (e.gizmo_dragging && p == e.gizmo_drag_axis) || (!e.gizmo_dragging && (int)p == hover_axis);
+ gizmo_draw_planar_quad(draw, f, p, active);
+ }
+}
+
+inline bool gizmo_center_active(const Editor &e, int hover_axis) {
+ return (e.gizmo_dragging && e.gizmo_drag_axis == GIZMO_FREE_AXIS) ||
+ (!e.gizmo_dragging && hover_axis == GIZMO_FREE_AXIS);
+}
+
+bool gizmo_rebase_if_moved(Editor &e, Scene &scene, GizmoFrame &f, int tool) {
+ if (e.gizmo_dragging &&
+ (e.selected_node != e.gizmo_grab_node || e.gizmo_tool_grab != tool || e.gizmo_world_grab != e.gizmo_world)) {
gizmo_rebase_drag(e, scene, f);
+ e.gizmo_tool_grab = tool;
+ e.gizmo_world_grab = e.gizmo_world;
+ return true;
}
+ return false;
+}
+
+void gizmo_end_drag(Editor &e) {
+ e.gizmo_dragging = false;
+ e.gizmo_grab_node = -1;
+ e.gizmo_tool_grab = -1;
+}
+
+bool gizmo_can_hover(const GizmoFrame &f, const Editor &e) {
+ return f.o_visible && f.hover_view && !e.gizmo_dragging;
+}
+
+bool gizmo_can_grab(const GizmoFrame &f, int hover_axis) {
+ return f.hover_view && hover_axis >= 0 && debug_finite_vec3(f.ray.dir) && debug_finite_vec3(f.ray.origin);
+}
+
+void update_move_gizmo(Editor &e, Scene &scene, DebugDraw &draw) {
+ GizmoFrame f;
+ if (!gizmo_begin_frame(e, scene, f)) {
+ return;
+ }
+ SceneGraph &sg = *f.sg;
+ const Camera &cam = *f.cam;
+
+ int hover_axis = gizmo_can_hover(f, e) ? gizmo_hover_move_scale(f, e) : -1;
+ e.gizmo_hover_axis = hover_axis;
+
+ gizmo_rebase_if_moved(e, scene, f, GIZMO_TOOL_MOVE);
bool mouse_down = ImGui::IsMouseDown(0);
bool clicked = ImGui::IsMouseClicked(0);
if (!e.gizmo_dragging) {
- if (clicked && f.hover_view && hover_axis >= 0 && debug_finite_vec3(f.ray.dir) &&
- debug_finite_vec3(f.ray.origin)) {
- // gizmo handle grab, axis = 0/1/2, center = 3
- e.gizmo_dragging = true;
- e.gizmo_grab_node = e.selected_node;
- e.gizmo_drag_axis = (u8)hover_axis;
- e.gizmo_base_local = sg.translations[f.node];
- e.gizmo_base_world = f.origin;
+ if (clicked && gizmo_can_grab(f, hover_axis)) {
+ gizmo_capture_common(e, sg, f.node, f.origin, f.base_mat, (u8)hover_axis);
+ e.gizmo_tool_grab = GIZMO_TOOL_MOVE;
+ e.gizmo_world_grab = e.gizmo_world;
if (e.gizmo_drag_axis == GIZMO_FREE_AXIS) {
e.gizmo_plane_n = cam.forward().normalized();
- e.gizmo_grab_point = ray_plane_point(f.origin, e.gizmo_plane_n, f.ray.origin, f.ray.dir, f.origin);
- } else {
- vec3 axis = gizmo_axis_dir(e.gizmo_drag_axis);
- e.gizmo_grab_point = closest_point_on_axis(f.origin, axis, f.ray.origin, f.ray.dir);
}
+ gizmo_derive_grab_refs(e, f, cam, f.origin, e.gizmo_drag_axis);
}
} else if (!mouse_down) {
- e.gizmo_dragging = false;
- e.gizmo_grab_node = -1;
+ gizmo_end_drag(e);
} else {
- // drag: current ref point -> world delta -> parent-space local write.
vec3 cur = e.gizmo_grab_point;
if (e.gizmo_drag_axis == GIZMO_FREE_AXIS) {
cur = ray_plane_point(e.gizmo_base_world, e.gizmo_plane_n, f.ray.origin, f.ray.dir, cur);
- } else {
- vec3 axis = gizmo_axis_dir(e.gizmo_drag_axis);
+ } else if (e.gizmo_drag_axis <= GIZMO_AXIS_Z) {
+ // axis may have rotated since grab
+ // reconstructed from current frame axes (rebase keeps them fresh)
+ vec3 axis = f.axes[e.gizmo_drag_axis];
cur = closest_point_on_axis(e.gizmo_base_world, axis, f.ray.origin, f.ray.dir);
+ } else {
+ cur = ray_plane_point(e.gizmo_base_world, e.gizmo_plane_n, f.ray.origin, f.ray.dir, cur);
}
vec3 new_world = e.gizmo_base_world + (cur - e.gizmo_grab_point);
if (e.gizmo_snap && e.gizmo_snap_step > 0.0f) {
@@ -75,25 +809,172 @@ void update_translate_gizmo(Editor &e, Scene &scene, DebugDraw &draw) {
gizmo_write_translation(sg, f.node, e.gizmo_base_local, e.gizmo_base_world, new_world);
}
- // handles (always drawn while a valid selection exists).
if (!f.o_visible) {
return;
}
- GizmoDrawScope tool_draw(draw);
+ gizmo_draw_axes(draw, f, e, hover_axis, [](DebugDraw &d, vec3 tip, f32 len, vec4 col, f32 w) {
+ d.cross(tip, len * 0.14f, col, DEBUG_CAT_GIZMO, DEBUG_DEPTH_XRAY, w);
+ });
+ gizmo_draw_planars(draw, f, e, hover_axis);
+ gizmo_draw_center_ring(draw, f, e, gizmo_center_active(e, hover_axis));
+}
+
+void update_rotate_gizmo(Editor &e, Scene &scene, DebugDraw &draw) {
+ GizmoFrame f;
+ if (!gizmo_begin_frame(e, scene, f)) {
+ return;
+ }
+ SceneGraph &sg = *f.sg;
+ const Camera &cam = *f.cam;
+
+ int hover_axis = gizmo_can_hover(f, e) ? gizmo_hit_rotate(f.mouse, f.o_px, f, e) : -1;
+ e.gizmo_hover_axis = hover_axis;
+
+ gizmo_rebase_if_moved(e, scene, f, GIZMO_TOOL_ROTATE);
+
+ bool mouse_down = ImGui::IsMouseDown(0);
+ bool clicked = ImGui::IsMouseClicked(0);
+
+ if (!e.gizmo_dragging) {
+ if (clicked && gizmo_can_grab(f, hover_axis)) {
+ gizmo_capture_common(e, sg, f.node, f.origin, f.base_mat, (u8)hover_axis);
+ e.gizmo_tool_grab = GIZMO_TOOL_ROTATE;
+ e.gizmo_world_grab = e.gizmo_world;
+ gizmo_derive_grab_refs(e, f, cam, f.origin, (u8)hover_axis);
+ }
+ } else if (!mouse_down) {
+ gizmo_end_drag(e);
+ } else {
+ vec3 p = ray_plane_point(e.gizmo_base_world, e.gizmo_plane_n, f.ray.origin, f.ray.dir, e.gizmo_grab_point);
+ f32 cur_ang = gizmo_angle_on_plane(p, e.gizmo_base_world, e.gizmo_drag_u, e.gizmo_drag_v);
+ f32 delta = gizmo_wrap_pi(cur_ang - e.gizmo_grab_angle);
+ if (e.gizmo_snap) {
+ f32 step = e.gizmo_snap_angle > 0.0f ? e.gizmo_snap_angle : 15.0f;
+ f32 deg = degrees(delta);
+ deg = roundf(deg / step) * step;
+ delta = radians(deg);
+ }
+ if (std::isfinite(delta) && fabsf(delta) > 1e-7f) {
+ gizmo_write_rotation(sg, f.node, e.gizmo_base_world_mat, e.gizmo_plane_n, delta);
+ }
+ }
+
+ if (!f.o_visible) {
+ return;
+ }
for (u8 a = 0; a < 3; ++a) {
bool active = (e.gizmo_dragging && a == e.gizmo_drag_axis) || (!e.gizmo_dragging && (int)a == hover_axis);
- vec4 col = active ? vec4(5.0f, 5.0f, 5.0f, 1.0f) : gizmo_axis_color(a);
- f32 w = active ? 4.0f : 3.0f;
- vec3 tip = f.origin + gizmo_axis_dir(a) * f.len;
- draw.line(f.origin, tip, col, DEBUG_CAT_DEFAULT, DEBUG_DEPTH_XRAY, w);
- draw.cross(tip, f.len * 0.14f, col, DEBUG_CAT_DEFAULT, DEBUG_DEPTH_XRAY, w);
+ gizmo_draw_ring(draw, f, a, f.axes[a], active);
+ }
+ bool view_active = (e.gizmo_dragging && e.gizmo_drag_axis == GIZMO_VIEW_AXIS) ||
+ (!e.gizmo_dragging && hover_axis == GIZMO_VIEW_AXIS);
+ gizmo_draw_view_ring(draw, f, cam, view_active);
+}
+
+void update_scale_gizmo(Editor &e, Scene &scene, DebugDraw &draw) {
+ GizmoFrame f;
+ if (!gizmo_begin_frame(e, scene, f)) {
+ return;
}
+ SceneGraph &sg = *f.sg;
+ const Camera &cam = *f.cam;
+
+ int hover_axis = gizmo_can_hover(f, e) ? gizmo_hover_move_scale(f, e) : -1;
+ e.gizmo_hover_axis = hover_axis;
+
+ gizmo_rebase_if_moved(e, scene, f, GIZMO_TOOL_SCALE);
+
+ bool mouse_down = ImGui::IsMouseDown(0);
+ bool clicked = ImGui::IsMouseClicked(0);
- bool center_active = (e.gizmo_dragging && e.gizmo_drag_axis == GIZMO_FREE_AXIS) ||
- (!e.gizmo_dragging && hover_axis == GIZMO_FREE_AXIS);
- gizmo_draw_center_ring(draw, f, e, center_active);
+ if (!e.gizmo_dragging) {
+ if (clicked && gizmo_can_grab(f, hover_axis)) {
+ u8 handle = (u8)hover_axis;
+ gizmo_capture_common(e, sg, f.node, f.origin, f.base_mat, handle);
+ e.gizmo_tool_grab = GIZMO_TOOL_SCALE;
+ e.gizmo_world_grab = e.gizmo_world;
+ if (handle == GIZMO_FREE_AXIS) {
+ e.gizmo_plane_n = cam.forward().normalized();
+ gizmo_derive_grab_refs(e, f, cam, f.origin, handle);
+ e.gizmo_grab_dist = (e.gizmo_grab_point - f.origin).length();
+ } else {
+ gizmo_derive_grab_refs(e, f, cam, f.origin, handle);
+ }
+ if (!(e.gizmo_grab_dist > 1e-6f) || !std::isfinite(e.gizmo_grab_dist)) {
+ e.gizmo_grab_dist = f.len * 0.5f;
+ if (!(e.gizmo_grab_dist > 1e-6f)) {
+ e.gizmo_grab_dist = 1.0f;
+ }
+ }
+ }
+ } else if (!mouse_down) {
+ gizmo_end_drag(e);
+ } else {
+ f32 factor = 1.0f;
+ if (e.gizmo_drag_axis <= GIZMO_AXIS_Z) {
+ vec3 axis = f.axes[e.gizmo_drag_axis];
+ f32 cur_dist = axis_signed_distance(e.gizmo_base_world, axis, f.ray.origin, f.ray.dir);
+ if (fabsf(e.gizmo_grab_dist) > 1e-6f && std::isfinite(cur_dist)) {
+ factor = cur_dist / e.gizmo_grab_dist;
+ }
+ } else {
+ vec3 n = e.gizmo_plane_n;
+ vec3 p = ray_plane_point(e.gizmo_base_world, n, f.ray.origin, f.ray.dir, e.gizmo_grab_point);
+ f32 r = (p - e.gizmo_base_world).length();
+ if (e.gizmo_grab_dist > 1e-6f && std::isfinite(r)) {
+ factor = r / e.gizmo_grab_dist;
+ }
+ }
+ if (e.gizmo_snap && e.gizmo_snap_scale > 0.0f) {
+ factor = roundf(factor / e.gizmo_snap_scale) * e.gizmo_snap_scale;
+ }
+ if (factor < 1e-4f) {
+ factor = 1e-4f;
+ }
+ if (std::isfinite(factor)) {
+ if (e.gizmo_world) {
+ gizmo_write_scale_world(sg, f.node, e.gizmo_base_world_mat, e.gizmo_drag_axis, factor);
+ } else {
+ gizmo_write_scale_local(sg, f.node, e.gizmo_base_scale, e.gizmo_drag_axis, factor);
+ }
+ }
+ }
+
+ if (!f.o_visible) {
+ return;
+ }
+ gizmo_draw_axes(draw, f, e, hover_axis, [](DebugDraw &d, vec3 tip, f32 len, vec4 col, f32 w) {
+ gizmo_draw_scale_cube(d, tip, len * 0.11f, col, w);
+ });
+ gizmo_draw_planars(draw, f, e, hover_axis);
+ bool center_active = gizmo_center_active(e, hover_axis);
+ vec4 ccol = center_active ? vec4(5.0f, 5.0f, 5.0f, 1.0f) : vec4(3.0f, 3.0f, 3.0f, 1.0f);
+ gizmo_draw_scale_cube(draw, f.origin, f.len * 0.12f, ccol, center_active ? 4.0f : 3.0f);
+}
+
+void update_gizmo(Editor &e, Scene &scene, DebugDraw &draw) {
+ // goto tool that owns an in-flight drag, or just the curr tool
+ int tool = (int)e.gizmo_tool;
+ if (e.gizmo_dragging && e.gizmo_tool_grab >= 0) {
+ tool = e.gizmo_tool_grab;
+ }
+ switch (tool) {
+ case GIZMO_TOOL_ROTATE:
+ update_rotate_gizmo(e, scene, draw);
+ break;
+ case GIZMO_TOOL_SCALE:
+ update_scale_gizmo(e, scene, draw);
+ break;
+ case GIZMO_TOOL_MOVE:
+ update_move_gizmo(e, scene, draw);
+ break;
+ default:
+ assert(false && "invalid gizmo tool");
+ update_move_gizmo(e, scene, draw);
+ break;
+ }
}
} // namespace editor
-#endif // ENABLE_EDITOR
+#endifDEL · src/editor/gizmo_common.h +0 -291--- a/src/editor/gizmo_common.h
+++ b/src/editor/gizmo_common.h
@@ -1,291 +0,0 @@
-#pragma once
-
-#ifdef ENABLE_EDITOR
-
-#include <cmath>
-
-#include "core/math_rtm.h"
-#include "imgui.h"
-#include "passes/debug/debug_draw.h"
-#include "passes/debug/debug_ray.h"
-#include "scene/camera.h"
-#include "scene/scene.h"
-
-#include "editor.h"
-
-namespace editor {
-
-// 0/1/2 = X/Y/Z axis handles, 3 = center free-move handle.
-constexpr u8 GIZMO_FREE_AXIS = 3;
-constexpr f32 GIZMO_AXIS_HIT_PX = 10.0f;
-constexpr f32 GIZMO_CENTER_RADIUS_PX = 10.0f;
-constexpr f32 GIZMO_CENTER_HIT_PX = 12.0f;
-constexpr f32 GIZMO_HANDLE_LEN_PX = 90.0f;
-constexpr u32 GIZMO_RING_SEGS = 24;
-constexpr f32 GIZMO_PI = 3.14159265358979323846f;
-
-// world-space axis directions (engine space: X fwd, Y right, Z up)
-inline vec3 gizmo_axis_dir(u8 a) {
- if (a == 0) {
- return vec3(1.0f, 0.0f, 0.0f);
- }
- if (a == 1) {
- return vec3(0.0f, 1.0f, 0.0f);
- }
- return vec3(0.0f, 0.0f, 1.0f);
-}
-
-// HDR-bright axis colors (grid convention: X red, Y green, Z blue).
-inline vec4 gizmo_axis_color(u8 a) {
- if (a == 0) {
- return vec4(4.0f, 0.6f, 0.6f, 1.0f);
- }
- if (a == 1) {
- return vec4(0.6f, 4.0f, 0.6f, 1.0f);
- }
- return vec4(0.6f, 0.8f, 4.0f, 1.0f);
-}
-
-inline vec3 selection_origin(const Scene &scene, u32 node) {
- const SceneGraph &sg = scene.scene_graph;
- mat4 base = (sg.instance_count[node] > 0) ? scene.instance_world(node, 0) : sg.worlds[node];
- vec4 h = transform_point(vec3(0.0f, 0.0f, 0.0f), base);
- return vec3(h.x, h.y, h.z);
-}
-
-inline bool project_to_view(vec3 p, const mat4 &view_proj, ImVec2 min, ImVec2 size, ImVec2 &out) {
- vec4 c = transform_point(p, view_proj);
- if (c.w <= 0.0f) { // behind camera
- return false;
- }
- f32 nx = c.x / c.w;
- f32 ny = c.y / c.w;
- out = ImVec2(min.x + (nx * 0.5f + 0.5f) * size.x, min.y + (ny * 0.5f + 0.5f) * size.y);
- return true;
-}
-
-inline f32 dist_point_seg(ImVec2 p, ImVec2 a, ImVec2 b) {
- f32 dx = b.x - a.x;
- f32 dy = b.y - a.y;
- f32 len2 = dx * dx + dy * dy;
- f32 t = (len2 > 1e-8f) ? ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2 : 0.0f;
- if (t < 0.0f) {
- t = 0.0f;
- } else if (t > 1.0f) {
- t = 1.0f;
- }
- f32 ex = a.x + dx * t - p.x;
- f32 ey = a.y + dy * t - p.y;
- return sqrtf(ex * ex + ey * ey);
-}
-
-// closest point on axis (origin + s*axis, |axis|==1) to ray (ray_o + t*ray_d).
-inline vec3 closest_point_on_axis(vec3 origin, vec3 axis, vec3 ray_o, vec3 ray_d) {
- vec3 w0 = origin - ray_o;
- f32 b = axis.dot(ray_d);
- f32 d = axis.dot(w0);
- f32 f = ray_d.dot(w0);
- f32 denom = 1.0f - b * b;
- f32 s = (fabsf(denom) < 1e-6f) ? 0.0f : (b * f - d) / denom;
- return origin + axis * s;
-}
-
-// ray plane intersection (point + t*n, |n|==1). Falls back to `fallback` when the ray
-// runs parallel to the plane or points away.
-inline vec3 ray_plane_point(vec3 point, vec3 n, vec3 ray_o, vec3 ray_d, vec3 fallback) {
- f32 denom = ray_d.dot(n);
- if (fabsf(denom) < 1e-6f) {
- return fallback;
- }
- f32 t = (point - ray_o).dot(n) / denom;
- if (!(t > 0.0f) || !std::isfinite(t)) {
- return fallback;
- }
- return ray_o + ray_d * t;
-}
-
-// per-frame gizmo context, computed once per tool update.
-struct GizmoFrame {
- u32 node = 0;
- SceneGraph *sg = nullptr;
- const Camera *cam = nullptr;
- vec3 origin = vec3(0.0f);
- f32 dist = 0.0f;
- f32 len = 0.0f; // screen-constant handle length (world units)
- ImVec2 mouse = ImVec2(0, 0);
- bool hover_view = false;
- ImVec2 o_px = ImVec2(0, 0);
- bool o_visible = false;
- DebugRay ray = {};
-};
-
-inline bool gizmo_begin_frame(Editor &e, Scene &scene, GizmoFrame &f) {
- if (!e.show_translate_gizmo || e.selected_node < 0 || (u32)e.selected_node >= scene.scene_graph.node_count) {
- e.gizmo_dragging = false;
- e.gizmo_grab_node = -1;
- e.gizmo_hover_axis = -1;
- return false;
- }
- SceneGraph &sg = scene.scene_graph;
- u32 node = (u32)e.selected_node;
- if (!sg.is_node_alive(node)) {
- e.gizmo_dragging = false;
- e.gizmo_grab_node = -1;
- e.gizmo_hover_axis = -1;
- return false;
- }
- if (scene.active_camera >= scene.cameras.size()) {
- e.gizmo_dragging = false;
- e.gizmo_grab_node = -1;
- e.gizmo_hover_axis = -1;
- return false;
- }
- if (e.gizmo_view_size.x <= 0.0f || e.gizmo_view_size.y <= 0.0f) {
- return false; // no viewport rect cached yet (first frame)
- }
- const Camera &cam = scene.cameras[scene.active_camera];
-
- vec3 origin = selection_origin(scene, node);
- vec3 to_cam = cam.position - origin;
- f32 dist = to_cam.length();
- if (!(dist > 0.0f) || !std::isfinite(dist)) {
- e.gizmo_dragging = false;
- e.gizmo_grab_node = -1;
- e.gizmo_hover_axis = -1;
- return false;
- }
- // screen-constant handle size (see pixels_to_world).
- f32 len = DebugDraw::pixels_to_world(dist, GIZMO_HANDLE_LEN_PX, cam.fov_y_deg, e.gizmo_view_size.y);
- if (!(len > 0.0f) || !std::isfinite(len)) {
- return false;
- }
-
- ImVec2 mouse = ImGui::GetMousePos();
- bool hover_view = mouse.x >= e.gizmo_view_min.x && mouse.x <= e.gizmo_view_min.x + e.gizmo_view_size.x &&
- mouse.y >= e.gizmo_view_min.y && mouse.y <= e.gizmo_view_min.y + e.gizmo_view_size.y;
-
- ImVec2 o_px{};
- bool o_visible = project_to_view(origin, cam.view_proj, e.gizmo_view_min, e.gizmo_view_size, o_px);
-
- // mouse ray in world space (for grab + drag).
- // NDC: up is y=-1.
- // imgui pixels map straight through, no y-flip
- f32 nx = ((mouse.x - e.gizmo_view_min.x) / e.gizmo_view_size.x) * 2.0f - 1.0f;
- f32 ny = ((mouse.y - e.gizmo_view_min.y) / e.gizmo_view_size.y) * 2.0f - 1.0f;
- DebugRay ray = debug_unproject(nx, ny, cam.position, cam.inv_view_proj);
-
- f.node = node;
- f.sg = &sg;
- f.cam = &cam;
- f.origin = origin;
- f.dist = dist;
- f.len = len;
- f.mouse = mouse;
- f.hover_view = hover_view;
- f.o_px = o_px;
- f.o_visible = o_visible;
- f.ray = ray;
- return true;
-}
-
-inline void gizmo_rebase_drag(Editor &e, Scene &scene, GizmoFrame &f) {
- SceneGraph &sg = *f.sg;
- bool alive = e.selected_node >= 0 && (u32)e.selected_node < sg.node_count && sg.is_node_alive((u32)e.selected_node);
- if (!alive || !debug_finite_vec3(f.ray.dir) || !debug_finite_vec3(f.ray.origin)) {
- e.gizmo_dragging = false;
- e.gizmo_grab_node = -1;
- return;
- }
- u32 node = (u32)e.selected_node;
- vec3 origin = selection_origin(scene, node);
- const Camera &cam = *f.cam;
- vec3 to_cam = cam.position - origin;
- f32 dist = to_cam.length();
- f32 len = DebugDraw::pixels_to_world(dist, GIZMO_HANDLE_LEN_PX, cam.fov_y_deg, e.gizmo_view_size.y);
- if (!(dist > 0.0f) || !std::isfinite(dist) || !(len > 0.0f) || !std::isfinite(len)) {
- e.gizmo_dragging = false;
- e.gizmo_grab_node = -1;
- return;
- }
- f.node = node;
- f.origin = origin;
- f.dist = dist;
- f.len = len;
- e.gizmo_grab_node = e.selected_node;
- e.gizmo_base_local = sg.translations[node];
- e.gizmo_base_world = origin;
- if (e.gizmo_drag_axis == GIZMO_FREE_AXIS) {
- e.gizmo_grab_point = ray_plane_point(origin, e.gizmo_plane_n, f.ray.origin, f.ray.dir, origin);
- } else {
- vec3 rebase_axis = gizmo_axis_dir(e.gizmo_drag_axis);
- e.gizmo_grab_point = closest_point_on_axis(origin, rebase_axis, f.ray.origin, f.ray.dir);
- }
-}
-
-inline void gizmo_write_translation(SceneGraph &sg, u32 node, vec3 base_local, vec3 base_world, vec3 new_world) {
- int p = sg.parent[node];
- mat4 parent_world = (p >= 0) ? sg.worlds[p] : identity();
- vec3 local_delta = transform_direction(new_world - base_world, inverse(parent_world));
- vec3 new_local = base_local + local_delta;
- if (debug_finite_vec3(new_local)) {
- sg.set_translation(node, new_local);
- }
-}
-
-struct GizmoDrawScope {
- DebugDraw &draw;
- u32 saved_mask;
- explicit GizmoDrawScope(DebugDraw &d) : draw(d), saved_mask(d.category_mask) {
- draw.category_mask = 0xFFFFFFFFu;
- }
- ~GizmoDrawScope() {
- draw.category_mask = saved_mask;
- }
-};
-
-inline bool gizmo_hit_center(ImVec2 mouse, ImVec2 o_px) {
- f32 dx = mouse.x - o_px.x;
- f32 dy = mouse.y - o_px.y;
- return sqrtf(dx * dx + dy * dy) < GIZMO_CENTER_HIT_PX;
-}
-
-inline int gizmo_hit_axis(ImVec2 mouse, ImVec2 o_px, const GizmoFrame &f, const Editor &e) {
- f32 best = GIZMO_AXIS_HIT_PX;
- int hit = -1;
- for (u8 a = 0; a < 3; ++a) {
- ImVec2 t_px{};
- if (!project_to_view(
- f.origin + gizmo_axis_dir(a) * f.len, f.cam->view_proj, e.gizmo_view_min, e.gizmo_view_size, t_px
- )) {
- continue;
- }
- f32 d = dist_point_seg(mouse, o_px, t_px);
- if (d < best) {
- best = d;
- hit = (int)a;
- }
- }
- return hit;
-}
-
-inline void gizmo_draw_center_ring(DebugDraw &draw, const GizmoFrame &f, const Editor &e, bool active) {
- vec4 col = active ? vec4(5.0f, 5.0f, 5.0f, 1.0f) : vec4(3.0f, 3.0f, 3.0f, 1.0f);
- f32 w = active ? 4.0f : 3.0f;
- f32 r = DebugDraw::pixels_to_world(f.dist, GIZMO_CENTER_RADIUS_PX, f.cam->fov_y_deg, e.gizmo_view_size.y);
- if (!(r > 0.0f) || !std::isfinite(r)) {
- return;
- }
- vec3 right = f.cam->right();
- vec3 up = f.cam->up();
- vec3 prev = f.origin + right * r;
- for (u32 i = 1; i <= GIZMO_RING_SEGS; ++i) {
- f32 t = (f32)i / (f32)GIZMO_RING_SEGS * 2.0f * GIZMO_PI;
- vec3 p = f.origin + (right * cosf(t) + up * sinf(t)) * r;
- draw.line(prev, p, col, DEBUG_CAT_DEFAULT, DEBUG_DEPTH_XRAY, w);
- prev = p;
- }
-}
-
-} // namespace editor
-
-#endif // ENABLE_EDITORMOD · src/editor/viewport.cpp +61 -20--- a/src/editor/viewport.cpp
+++ b/src/editor/viewport.cpp
@@ -41,39 +41,30 @@ void draw_viewport_central(Editor &e, RenderData &rd) {
ImGui::Begin("Viewport", nullptr);
- // translate-gizmo toolbar (widget state; drag math runs pre-execute in update_translate_gizmo)
- {
- bool move_on = e.show_translate_gizmo;
- if (ImGui::Button(move_on ? "Move: ON" : "Move")) {
- e.show_translate_gizmo = !e.show_translate_gizmo;
- }
- ImGui::SameLine();
- ImGui::Checkbox("Snap", &e.gizmo_snap);
- ImGui::SameLine();
- ImGui::SetNextItemWidth(64.0f);
- ImGui::DragFloat("##snapstep", &e.gizmo_snap_step, 0.01f, 0.01f, 10.0f, "%.2f");
- }
-
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);
- // rect cache for next frame's pre-execute gizmo update
e.gizmo_view_min = ImGui::GetItemRectMin();
e.gizmo_view_size = viewport_size;
- // a grab in progress owns the mouse, and handle presses are consumed
- // by the gizmo: never (de)select underneath a drag or a handle grab
- // (hover is 1 frame stale, same as the gizmo rect cache)
- if (ImGui::IsItemHovered() && ImGui::IsItemClicked(ImGuiMouseButton_Left) && !e.gizmo_dragging &&
- e.gizmo_hover_axis < 0 && rhi::valid(rd.id_blit)) {
+ 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; // I flip the vk viewport
+ ty = id_img.desc.height - 1 - ty; // Flip Vulkan viewport
rhi::ImageView id_view{};
if (rhi::create_image_view(
@@ -91,6 +82,56 @@ void draw_viewport_central(Editor &e, RenderData &rd) {
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);MOD · src/passes/debug/debug_draw.h +1 -3--- a/src/passes/debug/debug_draw.h
+++ b/src/passes/debug/debug_draw.h
@@ -16,9 +16,7 @@ enum DebugCategory : u8 {
DEBUG_CAT_SELECTION = 3,
DEBUG_CAT_LIGHT = 4,
DEBUG_CAT_DDGI = 5,
- DEBUG_CAT_GIZMO_X = 6, // reserved
- DEBUG_CAT_GIZMO_Y = 7, // reserved
- DEBUG_CAT_GIZMO_Z = 8, // reserved
+ DEBUG_CAT_GIZMO = 6,
};
enum DebugDepthMode : u8 {MOD · src/renderer.cpp +1 -1--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -1344,7 +1344,7 @@ int main() {
if (editor.show_debug_lines) {
ddgi_trace::debug_draw(render_data.debug_draw, render_data);
editor::debug_draw_selection(render_data.debug_draw, scene, render_data, editor.selected_node);
- editor::update_translate_gizmo(editor, scene, render_data.debug_draw);
+ editor::update_gizmo(editor, scene, render_data.debug_draw);
}
#endif