Browse Source

Merge pull request #58389 from rcorre/blender-numeric

Implement numeric blender-style transforms.
Rémi Verschelde 2 years ago
parent
commit
48a447b5ee
2 changed files with 165 additions and 114 deletions
  1. 154 114
      editor/plugins/node_3d_editor_plugin.cpp
  2. 11 0
      editor/plugins/node_3d_editor_plugin.h

+ 154 - 114
editor/plugins/node_3d_editor_plugin.cpp

@@ -1675,6 +1675,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 
 				if (_edit.mode != TRANSFORM_NONE && b->is_pressed()) {
 					cancel_transform();
+					break;
 				}
 
 				if (b->is_pressed()) {
@@ -2007,7 +2008,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 					_edit.mode = TRANSFORM_TRANSLATE;
 				}
 
-				if (_edit.mode == TRANSFORM_NONE) {
+				if (_edit.mode == TRANSFORM_NONE || _edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {
 					return;
 				}
 
@@ -2145,6 +2146,43 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 			return;
 		}
 
+		if (_edit.instant) {
+			// In a Blender-style transform, numbers set the magnitude of the transform.
+			// E.g. pressing g4.5x means "translate 4.5 units along the X axis".
+			// Use the Unicode value because we care about the text, not the actual keycode.
+			// This ensures numbers work consistently across different keyboard language layouts.
+			bool processed = true;
+			Key key = k->get_physical_keycode();
+			char32_t unicode = k->get_unicode();
+			if (unicode >= '0' && unicode <= '9') {
+				uint32_t value = uint32_t(unicode - Key::KEY_0);
+				if (_edit.numeric_next_decimal < 0) {
+					_edit.numeric_input = _edit.numeric_input + value * Math::pow(10.0, _edit.numeric_next_decimal--);
+				} else {
+					_edit.numeric_input = _edit.numeric_input * 10 + value;
+				}
+				update_transform_numeric();
+			} else if (unicode == '-') {
+				_edit.numeric_negate = !_edit.numeric_negate;
+				update_transform_numeric();
+			} else if (unicode == '.') {
+				if (_edit.numeric_next_decimal == 0) {
+					_edit.numeric_next_decimal = -1;
+				}
+			} else if (key == Key::ENTER || key == Key::KP_ENTER || key == Key::SPACE) {
+				commit_transform();
+			} else {
+				processed = false;
+			}
+
+			if (processed) {
+				// Ignore mouse inputs once we receive a numeric input.
+				set_process_input(false);
+				accept_event();
+				return;
+			}
+		}
+
 		if (EDITOR_GET("editors/3d/navigation/emulate_numpad")) {
 			const Key code = k->get_physical_keycode();
 			if (code >= Key::KEY_0 && code <= Key::KEY_9) {
@@ -2165,26 +2203,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 		} else {
 			// We're actively transforming, handle keys specially
 			TransformPlane new_plane = TRANSFORM_VIEW;
-			String new_message;
 			if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", p_event)) {
 				new_plane = TRANSFORM_X_AXIS;
-				new_message = TTR("X-Axis Transform.");
 			} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", p_event)) {
 				new_plane = TRANSFORM_Y_AXIS;
-				new_message = TTR("Y-Axis Transform.");
 			} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", p_event)) {
 				new_plane = TRANSFORM_Z_AXIS;
-				new_message = TTR("Z-Axis Transform.");
 			} else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense
 				if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", p_event)) {
 					new_plane = TRANSFORM_YZ;
-					new_message = TTR("YZ-Plane Transform.");
 				} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", p_event)) {
 					new_plane = TRANSFORM_XZ;
-					new_message = TTR("XZ-Plane Transform.");
 				} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", p_event)) {
 					new_plane = TRANSFORM_XY;
-					new_message = TTR("XY-Plane Transform.");
 				}
 			}
 
@@ -2201,8 +2232,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 					_edit.plane = TRANSFORM_VIEW;
 					spatial_editor->set_local_coords_enabled(false);
 				}
-				update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT));
-				set_message(new_message, 2);
+				if (_edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {
+					update_transform_numeric();
+				} else {
+					update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT));
+				}
 				accept_event();
 				return;
 			}
@@ -4575,6 +4609,43 @@ void Node3DEditorViewport::commit_transform() {
 	set_message("");
 }
 
+void Node3DEditorViewport::apply_transform(Vector3 p_motion, double p_snap) {
+	bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
+	List<Node *> &selection = editor_selection->get_selected_node_list();
+	for (Node *E : selection) {
+		Node3D *sp = Object::cast_to<Node3D>(E);
+		if (!sp) {
+			continue;
+		}
+
+		Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+		if (!se) {
+			continue;
+		}
+
+		if (sp->has_meta("_edit_lock_")) {
+			continue;
+		}
+
+		if (se->gizmo.is_valid()) {
+			for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
+				Transform3D xform = GE.value;
+				Transform3D new_xform = _compute_transform(_edit.mode, se->original * xform, xform, p_motion, p_snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo.
+				if (!local_coords) {
+					new_xform = se->original.affine_inverse() * new_xform;
+				}
+				se->gizmo->set_subgizmo_transform(GE.key, new_xform);
+			}
+		} else {
+			Transform3D new_xform = _compute_transform(_edit.mode, se->original, se->original_local, p_motion, p_snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW);
+			_transform_gizmo_apply(se->sp, new_xform, local_coords);
+		}
+	}
+
+	spatial_editor->update_transform_gizmo();
+	surface->queue_redraw();
+}
+
 // Update the current transform operation in response to an input.
 void Node3DEditorViewport::update_transform(bool p_shift) {
 	Vector3 ray_pos = _get_ray_pos(_edit.mouse_pos);
@@ -4670,43 +4741,11 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
 			set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
 					String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
 			if (local_coords) {
+				// TODO: needed?
 				motion = _edit.original.basis.inverse().xform(motion);
 			}
 
-			List<Node *> &selection = editor_selection->get_selected_node_list();
-			for (Node *E : selection) {
-				Node3D *sp = Object::cast_to<Node3D>(E);
-				if (!sp) {
-					continue;
-				}
-
-				Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
-				if (!se) {
-					continue;
-				}
-
-				if (sp->has_meta("_edit_lock_")) {
-					continue;
-				}
-
-				if (se->gizmo.is_valid()) {
-					for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
-						Transform3D xform = GE.value;
-						Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo.
-						if (!local_coords) {
-							new_xform = se->original.affine_inverse() * new_xform;
-						}
-						se->gizmo->set_subgizmo_transform(GE.key, new_xform);
-					}
-				} else {
-					Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW);
-					_transform_gizmo_apply(se->sp, new_xform, local_coords);
-				}
-			}
-
-			spatial_editor->update_transform_gizmo();
-			surface->queue_redraw();
-
+			apply_transform(motion, snap);
 		} break;
 
 		case TRANSFORM_TRANSLATE: {
@@ -4776,38 +4815,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
 				motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion);
 			}
 
-			List<Node *> &selection = editor_selection->get_selected_node_list();
-			for (Node *E : selection) {
-				Node3D *sp = Object::cast_to<Node3D>(E);
-				if (!sp) {
-					continue;
-				}
-
-				Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
-				if (!se) {
-					continue;
-				}
-
-				if (sp->has_meta("_edit_lock_")) {
-					continue;
-				}
-
-				if (se->gizmo.is_valid()) {
-					for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
-						Transform3D xform = GE.value;
-						Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords, true); // Force orthogonal with subgizmo.
-						new_xform = se->original.affine_inverse() * new_xform;
-						se->gizmo->set_subgizmo_transform(GE.key, new_xform);
-					}
-				} else {
-					Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS);
-					_transform_gizmo_apply(se->sp, new_xform, false);
-				}
-			}
-
-			spatial_editor->update_transform_gizmo();
-			surface->queue_redraw();
-
+			apply_transform(motion, snap);
 		} break;
 
 		case TRANSFORM_ROTATE: {
@@ -4876,53 +4884,85 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
 
 			bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW
 
-			List<Node *> &selection = editor_selection->get_selected_node_list();
-			for (Node *E : selection) {
-				Node3D *sp = Object::cast_to<Node3D>(E);
-				if (!sp) {
-					continue;
-				}
-
-				Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
-				if (!se) {
-					continue;
-				}
-
-				if (sp->has_meta("_edit_lock_")) {
-					continue;
-				}
-
-				Vector3 compute_axis = local_coords ? local_axis : global_axis;
-				if (se->gizmo.is_valid()) {
-					for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
-						Transform3D xform = GE.value;
-
-						Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords, true); // Force orthogonal with subgizmo.
-						if (!local_coords) {
-							new_xform = se->original.affine_inverse() * new_xform;
-						}
-						se->gizmo->set_subgizmo_transform(GE.key, new_xform);
-					}
-				} else {
-					Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS);
-					_transform_gizmo_apply(se->sp, new_xform, local_coords);
-				}
-			}
-
-			spatial_editor->update_transform_gizmo();
-			surface->queue_redraw();
-
+			Vector3 compute_axis = local_coords ? local_axis : global_axis;
+			apply_transform(compute_axis, angle);
 		} break;
 		default: {
 		}
 	}
 }
 
-// Perform cleanup after a transform operation is committed or canceled.
+void Node3DEditorViewport::update_transform_numeric() {
+	Vector3 motion;
+	switch (_edit.plane) {
+		case TRANSFORM_VIEW: {
+			switch (_edit.mode) {
+				case TRANSFORM_TRANSLATE:
+					motion = Vector3(1, 0, 0);
+					break;
+				case TRANSFORM_ROTATE:
+					motion = spatial_editor->get_gizmo_transform().basis.xform_inv(_get_camera_normal()).normalized();
+					break;
+				case TRANSFORM_SCALE:
+					motion = Vector3(1, 1, 1);
+					break;
+				case TRANSFORM_NONE:
+					ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");
+			}
+			break;
+		}
+		case TRANSFORM_X_AXIS:
+			motion = Vector3(1, 0, 0);
+			break;
+		case TRANSFORM_Y_AXIS:
+			motion = Vector3(0, 1, 0);
+			break;
+		case TRANSFORM_Z_AXIS:
+			motion = Vector3(0, 0, 1);
+			break;
+		case TRANSFORM_XY:
+			motion = Vector3(1, 1, 0);
+			break;
+		case TRANSFORM_XZ:
+			motion = Vector3(1, 0, 1);
+			break;
+		case TRANSFORM_YZ:
+			motion = Vector3(0, 1, 1);
+			break;
+	}
+
+	double value = _edit.numeric_input * (_edit.numeric_negate ? -1 : 1);
+	double extra = 0.0;
+	switch (_edit.mode) {
+		case TRANSFORM_TRANSLATE:
+			motion *= value;
+			set_message(vformat(TTR("Translating %s."), motion));
+			break;
+		case TRANSFORM_ROTATE:
+			extra = Math::deg_to_rad(value);
+			set_message(vformat(TTR("Rotating %f degrees."), value));
+			break;
+		case TRANSFORM_SCALE:
+			// To halve the size of an object in Blender, you scale it by 0.5.
+			// Doing the same in Godot is considered scaling it by -0.5.
+			motion *= (value - 1.0);
+			set_message(vformat(TTR("Scaling %s."), motion));
+			break;
+		case TRANSFORM_NONE:
+			ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");
+	}
+
+	apply_transform(motion, extra);
+}
+
+// Perform cleanup after a transform operation is committed or cancelled.
 void Node3DEditorViewport::finish_transform() {
-	spatial_editor->set_local_coords_enabled(_edit.original_local);
 	_edit.mode = TRANSFORM_NONE;
 	_edit.instant = false;
+	_edit.numeric_input = 0;
+	_edit.numeric_next_decimal = 0;
+	_edit.numeric_negate = false;
+	spatial_editor->set_local_coords_enabled(_edit.original_local);
 	spatial_editor->update_transform_gizmo();
 	surface->queue_redraw();
 	set_process_input(false);

+ 11 - 0
editor/plugins/node_3d_editor_plugin.h

@@ -349,6 +349,15 @@ private:
 		Variant gizmo_initial_value;
 		bool original_local;
 		bool instant;
+
+		// Numeric blender-style transforms (e.g. 'g5x').
+		// numeric_input tracks the current input value, e.g. 1.23.
+		// numeric_negate indicates whether '-' has been pressed to negate the value
+		// while numeric_next_decimal is 0, numbers are input before the decimal point
+		// after pressing '.', numeric next decimal changes to -1, and decrements after each press.
+		double numeric_input = 0.0;
+		bool numeric_negate = false;
+		int numeric_next_decimal = 0;
 	} _edit;
 
 	struct Cursor {
@@ -445,7 +454,9 @@ private:
 
 	void begin_transform(TransformMode p_mode, bool instant);
 	void commit_transform();
+	void apply_transform(Vector3 p_motion, double p_snap);
 	void update_transform(bool p_shift);
+	void update_transform_numeric();
 	void finish_transform();
 
 	void register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode, bool p_physical = false);