Add ability to transform parent nodes without affecting global transform of its children

Revert
This commit is contained in:
Robert Yevdokimov 2025-03-16 17:07:20 +04:00 committed by ryevdokimov
parent 220b0b2f74
commit e8e3cf1fdb
3 changed files with 125 additions and 0 deletions

View file

@ -625,6 +625,10 @@ void Node3DEditorViewport::cancel_transform() {
sp->set_global_transform(se->original);
}
for (const KeyValue<Node3D *, Transform3D> &pair : _edit.children_original_globals) {
pair.key->set_global_transform(pair.value);
}
collision_reposition = false;
finish_transform();
set_message(TTRC("Transform Aborted."), 3);
@ -1136,6 +1140,24 @@ void Node3DEditorViewport::_compute_edit(const Point2 &p_point) {
sel_item->original = sel_item->sp->get_global_gizmo_transform();
}
}
if (spatial_editor->is_preserve_children_transform_enabled() && _edit.children_original_globals.is_empty()) {
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
for (Node *E : selection) {
Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
int child_count = sp->get_child_count();
for (int i = 0; i < child_count; i++) {
Node3D *child = Object::cast_to<Node3D>(sp->get_child(i));
if (child) {
_edit.children_original_globals[child] = child->get_global_transform();
}
}
}
}
}
static Key _get_key_modifier_setting(const String &p_property) {
@ -1437,11 +1459,33 @@ void Node3DEditorViewport::_transform_gizmo_apply(Node3D *p_node, const Transfor
return;
}
bool preserve_children = spatial_editor->is_preserve_children_transform_enabled();
Vector<Transform3D> children_global_transforms;
Vector<Node3D *> node3d_children;
if (preserve_children) {
int child_count = p_node->get_child_count();
for (int i = 0; i < child_count; i++) {
Node3D *child = Object::cast_to<Node3D>(p_node->get_child(i));
if (child) {
children_global_transforms.push_back(child->get_global_transform());
node3d_children.push_back(child);
}
}
}
if (p_local) {
p_node->set_transform(p_transform);
} else {
p_node->set_global_transform(p_transform);
}
if (preserve_children) {
for (int i = 0; i < node3d_children.size(); i++) {
node3d_children[i]->set_global_transform(children_global_transforms[i]);
}
}
}
Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local, bool p_orthogonal, bool p_view_axis) {
@ -5160,6 +5204,8 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_
void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) {
if (get_selected_count() > 0) {
_edit.children_original_globals.clear();
_edit.mode = p_mode;
_compute_edit(_edit.mouse_pos);
_edit.instant = instant;
@ -5218,6 +5264,18 @@ void Node3DEditorViewport::commit_transform() {
undo_redo->add_do_method(sp, "set_transform", sp->get_local_gizmo_transform());
undo_redo->add_undo_method(sp, "set_transform", se->original_local);
}
if (!_edit.children_original_globals.is_empty()) {
for (const KeyValue<Node3D *, Transform3D> &pair : _edit.children_original_globals) {
Node3D *child = pair.key;
Transform3D original_global = pair.value;
Transform3D current_global = child->get_global_transform();
undo_redo->add_do_method(child, "set_global_transform", current_global);
undo_redo->add_undo_method(child, "set_global_transform", original_global);
}
}
undo_redo->commit_action();
collision_reposition = false;
@ -5646,6 +5704,7 @@ void Node3DEditorViewport::finish_transform() {
_edit.accumulated_rotation_angle = 0.0;
_edit.rotation_angle = 0.0;
_edit.gizmo_initiated = false;
_edit.children_original_globals.clear();
spatial_editor->set_local_coords_enabled(_edit.original_local);
spatial_editor->update_transform_gizmo();
surface->queue_redraw();
@ -6692,6 +6751,7 @@ Dictionary Node3DEditor::get_state() const {
d["scale_snap"] = snap_scale_value;
d["local_coords"] = tool_option_button[TOOL_OPT_LOCAL_COORDS]->is_pressed();
d["preserve_children_transform"] = tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->is_pressed();
int vc = 0;
if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {
@ -6786,6 +6846,10 @@ void Node3DEditor::set_state(const Dictionary &p_state) {
_snap_update();
if (d.has("preserve_children_transform")) {
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_pressed(d["preserve_children_transform"]);
}
if (d.has("local_coords")) {
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(d["local_coords"]);
update_transform_gizmo();
@ -7028,6 +7092,47 @@ void Node3DEditor::_menu_item_toggled(bool pressed, int p_option) {
viewports[i]->update_transform_gizmo_highlight();
}
} break;
case MENU_TOOL_PRESERVE_CHILDREN_TRANSFORM: {
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_pressed(pressed);
if (pressed) {
EditorNode::get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &Node3DEditor::_undo_redo_inspector_callback));
} else {
EditorNode::get_editor_data().remove_undo_redo_inspector_hook_callback(callable_mp(this, &Node3DEditor::_undo_redo_inspector_callback));
}
} break;
}
}
void Node3DEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, const String &p_property, const Variant &p_new_value) {
Node3D *node = Object::cast_to<Node3D>(p_edited);
if (!node) {
return;
}
static const char *transform_properties[] = { "position", "rotation", "scale", "quaternion", "basis", "transform", nullptr };
bool is_transform_prop = false;
for (int i = 0; transform_properties[i]; i++) {
if (p_property == transform_properties[i]) {
is_transform_prop = true;
break;
}
}
if (!is_transform_prop) {
return;
}
EditorUndoRedoManager *undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo);
ERR_FAIL_NULL(undo_redo);
int child_count = node->get_child_count();
for (int i = 0; i < child_count; i++) {
Node3D *child = Object::cast_to<Node3D>(node->get_child(i));
if (child) {
Transform3D child_global = child->get_global_transform();
undo_redo->add_do_method(child, "set_global_transform", child_global);
undo_redo->add_undo_method(child, "set_global_transform", child_global);
}
}
}
@ -8619,6 +8724,7 @@ void Node3DEditor::_update_theme() {
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_button_icon(get_editor_theme_icon(SNAME("Object")));
tool_option_button[TOOL_OPT_USE_SNAP]->set_button_icon(get_editor_theme_icon(SNAME("Snap")));
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_button_icon(get_editor_theme_icon(SNAME("Trackball")));
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_editor_theme_icon(SNAME("Panels1")));
view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_editor_theme_icon(SNAME("Panels2")));
@ -9596,6 +9702,16 @@ Node3DEditor::Node3DEditor() {
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_shortcut_context(this);
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_accessibility_name(TTRC("Use Trackball"));
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM] = memnew(Button);
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]);
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_toggle_mode(true);
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_theme_type_variation(SceneStringName(FlatButton));
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_PRESERVE_CHILDREN_TRANSFORM));
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_shortcut(ED_SHORTCUT("spatial_editor/preserve_children_transform", TTRC("Preserve Children Transform"), Key::P));
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_shortcut_context(this);
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_accessibility_name(TTRC("Preserve Children Transform"));
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_tooltip_text(TTRC("When enabled, transforming a node will preserve the global transform of its children.\nThis also applies when editing transform properties in the Inspector."));
main_menu_hbox->add_child(memnew(VSeparator));
sun_button = memnew(Button);
sun_button->set_tooltip_text(TTRC("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled."));

View file

@ -356,6 +356,8 @@ private:
Vector3 initial_click_vector;
Vector3 previous_rotation_vector;
bool gizmo_initiated = false;
HashMap<Node3D *, Transform3D> children_original_globals;
} _edit;
Ref<View3DController> view_3d_controller;
@ -583,6 +585,7 @@ public:
TOOL_OPT_LOCAL_COORDS,
TOOL_OPT_USE_SNAP,
TOOL_OPT_USE_TRACKBALL,
TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM,
TOOL_OPT_MAX
};
@ -686,6 +689,7 @@ private:
MENU_TOOL_LOCAL_COORDS,
MENU_TOOL_USE_SNAP,
MENU_TOOL_USE_TRACKBALL,
MENU_TOOL_PRESERVE_CHILDREN_TRANSFORM,
MENU_TRANSFORM_CONFIGURE_SNAP,
MENU_TRANSFORM_DIALOG,
MENU_VIEW_USE_1_VIEWPORT,
@ -877,6 +881,8 @@ private:
void _update_theme();
void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, const String &p_property, const Variant &p_new_value);
protected:
void _notification(int p_what);
//void _gui_input(InputEvent p_event);
@ -901,6 +907,7 @@ public:
ToolMode get_tool_mode() const { return tool_mode; }
bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); }
void set_local_coords_enabled(bool on) const { tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_pressed(on); }
bool is_preserve_children_transform_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->is_pressed(); }
bool is_snap_enabled() const { return snap_enabled ^ snap_key_enabled; }
real_t get_translate_snap() const;
real_t get_rotate_snap() const;

View file

@ -801,6 +801,8 @@ void CSGShape3D::update_shape() {
set_base(root_mesh->get_rid());
update_gizmos();
#ifndef PHYSICS_3D_DISABLED
_update_collision_faces();
#endif // PHYSICS_3D_DISABLED