Skip to content

Commit a1a80d7

Browse files
committed
Add snapping to the transform gizmo
1 parent 61973ff commit a1a80d7

File tree

6 files changed

+433
-8
lines changed

6 files changed

+433
-8
lines changed

editor/viewport/editor_main_screen_nd.cpp

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ void EditorMainScreenND::_on_transform_settings_menu_id_pressed(const int p_id)
119119
_nd_editor_config_file->set_value("transform", "keep_mode", (int)keep_mode);
120120
}
121121
_nd_editor_config_file->save(_nd_editor_config_file_path);
122+
} else if (p_id == TRANSFORM_SETTING_CONFIGURE_SNAP) {
123+
_snap_settings_dialog->popup_centered(Size2(400, 450) * EDSCALE);
122124
}
123125
}
124126

@@ -261,6 +263,9 @@ void EditorMainScreenND::_notification(int p_what) {
261263
} break;
262264
#if GODOT_MODULE || (GODOT_VERSION_MAJOR > 4 || (GODOT_VERSION_MAJOR == 4 && GODOT_VERSION_MINOR >= 4))
263265
case NOTIFICATION_EXIT_TREE: {
266+
if (_snap_settings_inspector != nullptr) {
267+
_snap_settings_inspector->edit(nullptr);
268+
}
264269
if (_camera_settings_inspector != nullptr) {
265270
_camera_settings_inspector->edit(nullptr);
266271
}
@@ -398,6 +403,7 @@ void EditorMainScreenND::setup(EditorUndoRedoManager *p_undo_redo_manager) {
398403
// All viewports share one gizmo and origin marker.
399404
_transform_gizmo_nd = memnew(EditorTransformGizmoND);
400405
add_child(_transform_gizmo_nd);
406+
_transform_gizmo_nd->setup(this, p_undo_redo_manager);
401407

402408
_origin_marker = memnew(MarkerND);
403409
_origin_marker->set_name(StringName("OriginMarkerND"));
@@ -428,8 +434,29 @@ void EditorMainScreenND::setup(EditorUndoRedoManager *p_undo_redo_manager) {
428434
transform_settings_menu_popup->add_radio_check_item(TTR("Keep Conformal (uniform scale)"), TRANSFORM_SETTING_KEEP_CONFORMAL);
429435
transform_settings_menu_popup->add_radio_check_item(TTR("Keep Orthonormal (no scale)"), TRANSFORM_SETTING_KEEP_ORTHONORMAL);
430436
transform_settings_menu_popup->set_item_checked((int)EditorTransformGizmoND::KeepMode::FREEFORM, true);
437+
transform_settings_menu_popup->add_separator();
438+
transform_settings_menu_popup->add_item(TTR("Configure Snap..."), TRANSFORM_SETTING_CONFIGURE_SNAP);
431439
transform_settings_menu_popup->connect(StringName("id_pressed"), callable_mp(this, &EditorMainScreenND::_on_transform_settings_menu_id_pressed));
432440

441+
#if GODOT_MODULE || (GODOT_VERSION_MAJOR > 4 || (GODOT_VERSION_MAJOR == 4 && GODOT_VERSION_MINOR >= 4))
442+
// Set up the snap settings dialog as long as this is Godot >= 4.4.
443+
EditorTransformSnapSettingsND *snap_settings = _transform_gizmo_nd->get_snap_settings();
444+
snap_settings->setup(_nd_editor_config_file, _nd_editor_config_file_path);
445+
_snap_settings_dialog = memnew(ConfirmationDialog);
446+
_snap_settings_dialog->set_name(StringName("SnapSettingsDialog"));
447+
_snap_settings_dialog->set_title(TTR("Transform Gizmo Snap Settings"));
448+
_snap_settings_inspector = memnew(EditorInspector);
449+
_snap_settings_inspector->set_name(StringName("SnapSettingsInspector"));
450+
_snap_settings_inspector->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
451+
_snap_settings_inspector->set_custom_minimum_size(Size2(400, 200) * EDSCALE);
452+
_snap_settings_inspector->edit(snap_settings);
453+
_snap_settings_dialog->add_child(_snap_settings_inspector);
454+
add_child(_snap_settings_dialog);
455+
#else
456+
transform_settings_menu_popup->set_item_disabled(TRANSFORM_SETTING_CONFIGURE_SNAP, true);
457+
transform_settings_menu_popup->set_item_tooltip(TRANSFORM_SETTING_CONFIGURE_SNAP, TTR("Snap settings requires compiling with a minimum Godot version of 4.4, but this was compiled for Godot " + itos(GODOT_VERSION_MAJOR) + "." + itos(GODOT_VERSION_MINOR) + "."));
458+
#endif
459+
433460
// Set up the Layout menu in the toolbar.
434461
_layout_menu = memnew(MenuButton);
435462
_layout_menu->set_flat(false);
@@ -503,9 +530,6 @@ void EditorMainScreenND::setup(EditorUndoRedoManager *p_undo_redo_manager) {
503530

504531
EditorInterface::get_singleton()->get_selection()->connect(StringName("selection_changed"), callable_mp(this, &EditorMainScreenND::_on_selection_changed));
505532

506-
// Set up things with the arguments (not constructor things).
507-
_transform_gizmo_nd->setup(this, p_undo_redo_manager);
508-
509533
_apply_nd_editor_settings();
510534
}
511535

editor/viewport/editor_main_screen_nd.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class EditorMainScreenND : public Control {
5656
TRANSFORM_SETTING_KEEP_CONFORMAL, // 2
5757
TRANSFORM_SETTING_KEEP_ORTHONORMAL, // 3
5858
TRANSFORM_SETTING_KEEP_MAX, // 4
59+
TRANSFORM_SETTING_CONFIGURE_SNAP = TRANSFORM_SETTING_KEEP_MAX, // Still 4
5960
};
6061

6162
enum LayoutItem {
@@ -80,6 +81,8 @@ class EditorMainScreenND : public Control {
8081
MenuButton *_layout_menu = nullptr;
8182
MenuButton *_view_menu = nullptr;
8283
PopupMenu *_rendering_engine_menu_popup = nullptr;
84+
ConfirmationDialog *_snap_settings_dialog = nullptr;
85+
EditorInspector *_snap_settings_inspector = nullptr;
8386
ConfirmationDialog *_camera_settings_dialog = nullptr;
8487
EditorInspector *_camera_settings_inspector = nullptr;
8588
EditorCameraSettingsND *_camera_settings = nullptr;

editor/viewport/editor_transform_gizmo_nd.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ void EditorTransformGizmoND::_update_gizmo_transform() {
295295
set_visible(false);
296296
} else {
297297
set_visible(true);
298+
sum_transform->set_dimension(sum_transform->get_dimension()); // Make sure the matrix is square.
298299
sum_transform = sum_transform->divide_scalar(double(transform_count));
299300
if (_is_use_local_rotation) {
300301
// Scale/shear/skew can mess with the rotation gizmo, so get rid of it.
@@ -583,7 +584,7 @@ void EditorTransformGizmoND::_end_transformation() {
583584
}
584585
// Create an undo/redo action for the transformation.
585586
const String action = _get_transform_part_simple_action_name(_current_transformation);
586-
_undo_redo->create_action(action + String(" 4D nodes with gizmo"));
587+
_undo_redo->create_action(action + String(" ND nodes with gizmo"));
587588
const int size = _selected_top_nodes.size();
588589
for (int i = 0; i < size; i++) {
589590
NodeND *node_nd = Object::cast_to<NodeND>(_selected_top_nodes[i]);
@@ -660,7 +661,12 @@ void EditorTransformGizmoND::_process_transform(const VectorN &p_local_ray_origi
660661
// The above position changes happen relative to the visual gizmo mesh holder, but
661662
// we want them relative to the gizmo itself. Scale/rotation should not be adjusted.
662663
transform_change->set_origin(_old_mesh_holder_transform->xform(transform_change->get_origin()));
663-
Ref<TransformND> new_transform = _old_gizmo_transform->compose_square(transform_change);
664+
transform_change->set_dimension(transform_change->get_dimension()); // Make sure the matrix is square.
665+
Ref<TransformND> new_transform = _snap_settings->snap_transform_change(_old_gizmo_transform, transform_change);
666+
// Special case: Only in move mode, ignore any snapping that happened to the basis.
667+
if (_current_transformation == TRANSFORM_MOVE_AXIS || _current_transformation == TRANSFORM_MOVE_PLANE) {
668+
new_transform->set_basis(_old_gizmo_transform->get_basis());
669+
}
664670
set_transform(new_transform);
665671
// We want the global diff so we can apply it from the left on the global transform of all selected nodes.
666672
// Without this, the transforms would be relative to each node (ex: moving on X moves on each node's X axis).
@@ -669,6 +675,11 @@ void EditorTransformGizmoND::_process_transform(const VectorN &p_local_ray_origi
669675
NodeND *node_nd = Object::cast_to<NodeND>(_selected_top_nodes[i]);
670676
if (node_nd != nullptr) {
671677
node_nd->set_global_transform(transform_change->compose_square(_selected_top_node_old_transforms[i]));
678+
if (_snap_settings->get_snap_final_values()) {
679+
node_nd->set_transform(_snap_settings->snap_single_transform(node_nd->get_transform()));
680+
}
681+
// Note: The keep mode takes priority over snapping, so we apply it after snapping.
682+
// In most cases these won't conflict, but in an edge case where they do, keep mode wins.
672683
switch (_keep_mode) {
673684
case KeepMode::FREEFORM: {
674685
// Do nothing.
@@ -832,10 +843,10 @@ bool EditorTransformGizmoND::gizmo_mouse_input(const Ref<InputEventMouse> &p_mou
832843
const Ref<TransformND> global_to_local = _old_gizmo_transform->compose_square(_old_mesh_holder_transform)->inverse();
833844
const VectorN local_ray_origin = global_to_local->xform(ray_origin);
834845
const VectorN local_ray_direction = global_to_local->xform_basis(ray_direction);
835-
return gizmo_mouse_raycast(p_mouse_event, local_ray_origin, local_ray_direction);
846+
return _gizmo_mouse_raycast(p_mouse_event, p_camera, local_ray_origin, local_ray_direction);
836847
}
837848

838-
bool EditorTransformGizmoND::gizmo_mouse_raycast(const Ref<InputEventMouse> &p_mouse_event, const VectorN &p_local_ray_origin, const VectorN &p_local_ray_direction) {
849+
bool EditorTransformGizmoND::_gizmo_mouse_raycast(const Ref<InputEventMouse> &p_mouse_event, const CameraND *p_camera, const VectorN &p_local_ray_origin, const VectorN &p_local_ray_direction) {
839850
Ref<InputEventMouseButton> mouse_button = p_mouse_event;
840851
if (mouse_button.is_valid()) {
841852
if (mouse_button->get_button_index() != MOUSE_BUTTON_LEFT) {
@@ -850,6 +861,8 @@ bool EditorTransformGizmoND::gizmo_mouse_raycast(const Ref<InputEventMouse> &p_m
850861
_primary_axis = new_primary_axis;
851862
_secondary_axis = new_secondary_axis;
852863
_begin_transformation(p_local_ray_origin, p_local_ray_direction);
864+
const double dist = VectorND::distance_to(_old_gizmo_transform->get_origin(), p_camera->get_global_position());
865+
_snap_settings->set_camera_distance(dist);
853866
}
854867
} else if (!mouse_button->is_pressed() && _current_transformation != TRANSFORM_NONE) {
855868
// If we are transforming something and the user releases the click, end the transformation.
@@ -901,6 +914,7 @@ void EditorTransformGizmoND::setup(EditorMainScreenND *p_editor_main_screen, Edi
901914
_mesh_holder = memnew(NodeND);
902915
_mesh_holder->set_name(StringName("GizmoMeshHolderND"));
903916
add_child(_mesh_holder);
917+
_snap_settings = memnew(EditorTransformSnapSettingsND);
904918
RenderingServerND::get_singleton()->connect(StringName("pre_render"), callable_mp(this, &EditorTransformGizmoND::_on_rendering_server_pre_render));
905919
EditorInterface::get_singleton()->get_inspector()->connect(StringName("property_edited"), callable_mp(this, &EditorTransformGizmoND::_on_editor_inspector_property_edited));
906920

@@ -909,3 +923,9 @@ void EditorTransformGizmoND::setup(EditorMainScreenND *p_editor_main_screen, Edi
909923
_undo_redo = p_undo_redo_manager;
910924
p_undo_redo_manager->connect(StringName("version_changed"), callable_mp(this, &EditorTransformGizmoND::_on_undo_redo_version_changed));
911925
}
926+
927+
EditorTransformGizmoND::~EditorTransformGizmoND() {
928+
if (_snap_settings != nullptr) {
929+
memdelete(_snap_settings);
930+
}
931+
}

editor/viewport/editor_transform_gizmo_nd.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include "editor_viewport_nd_defines.h"
44

5+
#include "editor_transform_snap_settings_nd.h"
6+
57
#include "../../math/rect_nd.h"
68
#include "../../model/mesh_instance_nd.h"
79

@@ -47,6 +49,7 @@ class EditorTransformGizmoND : public NodeND {
4749
Vector<NodeND *> _mesh_keep_conformal;
4850
Vector<MeshInstanceND *> _meshes[TRANSFORM_MAX];
4951
EditorMainScreenND *_editor_main_screen = nullptr;
52+
EditorTransformSnapSettingsND *_snap_settings = nullptr;
5053
EditorUndoRedoManager *_undo_redo = nullptr;
5154

5255
KeepMode _keep_mode = KeepMode::FREEFORM;
@@ -77,6 +80,7 @@ class EditorTransformGizmoND : public NodeND {
7780
void _regenerate_gizmo_meshes();
7881

7982
// Misc internal functions.
83+
bool _gizmo_mouse_raycast(const Ref<InputEventMouse> &p_mouse_event, const CameraND *p_camera, const VectorN &p_ray_origin, const VectorN &p_ray_direction);
8084
void _on_rendering_server_pre_render(CameraND *p_camera, Viewport *p_viewport, RenderingEngineND *p_rendering_engine);
8185
void _on_editor_inspector_property_edited(const String &p_prop);
8286
void _on_undo_redo_version_changed();
@@ -102,16 +106,17 @@ class EditorTransformGizmoND : public NodeND {
102106
static void _bind_methods() {}
103107

104108
public:
109+
EditorTransformSnapSettingsND *get_snap_settings() const { return _snap_settings; }
105110
void selected_nodes_changed(const TypedArray<Node> &p_top_nodes);
106111
void set_axis_colors(const PackedColorArray &p_axis_colors);
107112
void set_keep_mode(const KeepMode p_mode);
108113
void set_gizmo_mode(const GizmoMode p_mode);
109114
void set_gizmo_dimension(const int p_dimension);
110115
bool gizmo_mouse_input(const Ref<InputEventMouse> &p_mouse_event, const CameraND *p_camera);
111-
bool gizmo_mouse_raycast(const Ref<InputEventMouse> &p_mouse_event, const VectorN &p_ray_origin, const VectorN &p_ray_direction);
112116

113117
bool get_use_local_rotation() const;
114118
void set_use_local_rotation(const bool p_use_local_transform);
115119

116120
void setup(EditorMainScreenND *p_editor_main_screen, EditorUndoRedoManager *p_undo_redo_manager);
121+
~EditorTransformGizmoND();
117122
};

0 commit comments

Comments
 (0)