Skip to content

Commit

Permalink
Add "Follow Selection" in the 3D editor by using Center Selection twice
Browse files Browse the repository at this point in the history
When pressing the Center Selection shortcut twice, you will begin following
the current selection. This also applies to selection changes.

The effect is undone by pressing the Center Selection shortcut another
time, or by panning/using freelook on the 3D editor camera. (Orbiting
does not undo the effect.)
  • Loading branch information
Calinou committed Nov 21, 2024
1 parent 9e60984 commit f8fc45d
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
38 changes: 38 additions & 0 deletions editor/plugins/node_3d_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2317,21 +2317,27 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (ED_IS_SHORTCUT("spatial_editor/bottom_view", p_event)) {
_menu_option(VIEW_BOTTOM);
_disable_follow_mode();
}
if (ED_IS_SHORTCUT("spatial_editor/top_view", p_event)) {
_menu_option(VIEW_TOP);
_disable_follow_mode();
}
if (ED_IS_SHORTCUT("spatial_editor/rear_view", p_event)) {
_menu_option(VIEW_REAR);
_disable_follow_mode();
}
if (ED_IS_SHORTCUT("spatial_editor/front_view", p_event)) {
_menu_option(VIEW_FRONT);
_disable_follow_mode();
}
if (ED_IS_SHORTCUT("spatial_editor/left_view", p_event)) {
_menu_option(VIEW_LEFT);
_disable_follow_mode();
}
if (ED_IS_SHORTCUT("spatial_editor/right_view", p_event)) {
_menu_option(VIEW_RIGHT);
_disable_follow_mode();
}
if (ED_IS_SHORTCUT("spatial_editor/orbit_view_down", p_event)) {
// Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
Expand Down Expand Up @@ -2362,9 +2368,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (ED_IS_SHORTCUT("spatial_editor/focus_origin", p_event)) {
_menu_option(VIEW_CENTER_TO_ORIGIN);
_disable_follow_mode();
}
if (ED_IS_SHORTCUT("spatial_editor/focus_selection", p_event)) {
_menu_option(VIEW_CENTER_TO_SELECTION);
times_focused_consecutively += 1;
}
if (ED_IS_SHORTCUT("spatial_editor/align_transform_with_view", p_event)) {
_menu_option(VIEW_ALIGN_TRANSFORM_WITH_VIEW);
Expand Down Expand Up @@ -2519,6 +2527,8 @@ void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const
translation *= cursor.distance / DISTANCE_DEFAULT;
camera_transform.translate_local(translation);
cursor.pos = camera_transform.origin;

_disable_follow_mode();
}

void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
Expand Down Expand Up @@ -2781,6 +2791,8 @@ void Node3DEditorViewport::_update_freelook(real_t delta) {
const Vector3 motion = direction * speed * delta;
cursor.pos += motion;
cursor.eye_pos += motion;

_disable_follow_mode();
}

void Node3DEditorViewport::set_message(const String &p_message, float p_time) {
Expand Down Expand Up @@ -2892,6 +2904,15 @@ void Node3DEditorViewport::_notification(int p_what) {
_update_navigation_controls_visibility();
_update_freelook(delta);

if (focused_node && get_selected_count() > 0 && times_focused_consecutively >= 2 && times_focused_consecutively % 2 == 0) {
// Clearing the selection resets focus
follow_mode->set_text(vformat(TTR("Following %s"), focused_node->get_name()));
follow_mode->show();
focus_selection();
} else {
follow_mode->hide();
}

Node *scene_root = SceneTreeDock::get_singleton()->get_editor_data()->get_edited_scene_root();
if (previewing_cinema && scene_root != nullptr) {
Camera3D *cam = scene_root->get_viewport()->get_camera_3d();
Expand Down Expand Up @@ -3862,6 +3883,11 @@ void Node3DEditorViewport::_finish_gizmo_instances() {
RS::get_singleton()->free(rotate_gizmo_instance[3]);
}

void Node3DEditorViewport::_disable_follow_mode() {
// Exit follow mode by resetting the number of times the follow shortcut was used consecutively.
times_focused_consecutively = 0;
}

void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {
ERR_FAIL_COND(p_activate && !preview);
ERR_FAIL_COND(!p_activate && !previewing);
Expand Down Expand Up @@ -4231,6 +4257,10 @@ void Node3DEditorViewport::focus_selection() {
int count = 0;

const List<Node *> &selection = editor_selection->get_selected_node_list();
focused_node = nullptr;
if (!selection.is_empty()) {
focused_node = selection.get(0);
}

for (Node *E : selection) {
Node3D *sp = Object::cast_to<Node3D>(E);
Expand Down Expand Up @@ -5510,6 +5540,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_origin"), VIEW_CENTER_TO_ORIGIN);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_selection"), VIEW_CENTER_TO_SELECTION);
view_menu->get_popup()->set_item_tooltip(-1, TTR("Press Focus Selection twice to start following the selection as it moves. Press it yet another time to exit following."));
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_transform_with_view"), VIEW_ALIGN_TRANSFORM_WITH_VIEW);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_rotation_with_view"), VIEW_ALIGN_ROTATION_WITH_VIEW);
view_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_menu_option));
Expand Down Expand Up @@ -5565,6 +5596,13 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
ED_SHORTCUT("spatial_editor/instant_scale", TTR("Begin Scale Transformation"));
ED_SHORTCUT("spatial_editor/collision_reposition", TTR("Reposition Using Collisions"), KeyModifierMask::SHIFT | Key::G);

follow_mode = memnew(Button);
follow_mode->set_tooltip_text(TTR("Click to stop following this node as it moves."));
follow_mode->set_theme_type_variation("FlatButton");
follow_mode->hide();
vbox->add_child(follow_mode);
follow_mode->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditorViewport::_disable_follow_mode));

preview_camera = memnew(CheckBox);
preview_camera->set_text(TTR("Preview"));
// Using Control even on macOS to avoid conflict with Quick Open shortcut.
Expand Down
6 changes: 6 additions & 0 deletions editor/plugins/node_3d_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,11 @@ class Node3DEditorViewport : public Control {
Node *target_node = nullptr;
Point2 drop_pos;

Node *focused_node = nullptr;

EditorSelection *editor_selection = nullptr;

Button *follow_mode = nullptr;
CheckBox *preview_camera = nullptr;
SubViewportContainer *subviewport_container = nullptr;

Expand Down Expand Up @@ -462,6 +465,7 @@ class Node3DEditorViewport : public Control {
bool previewing_cinema = false;
bool _is_node_locked(const Node *p_node) const;
void _preview_exited_scene();
void _disable_follow_mode();
void _toggle_camera_preview(bool);
void _toggle_cinema_preview(bool);
void _init_gizmo_instance(int p_idx);
Expand Down Expand Up @@ -525,6 +529,8 @@ class Node3DEditorViewport : public Control {

void focus_selection();

int times_focused_consecutively = 0;

void assign_pending_data_pointers(
Node3D *p_preview_node,
AABB *p_preview_bounds,
Expand Down

0 comments on commit f8fc45d

Please sign in to comment.