Skip to content

Commit

Permalink
Implement numeric blender-style transforms.
Browse files Browse the repository at this point in the history
This allows the user to input numbers during an "instant" (blender
style) transform operation to specify exactly how far to transform the
object. For example:

g2.5xx: Translate 2.5 units along the local x-axis
ry-45: Rotate -45 degrees around the y-axis
s.25Z: Scale by a factor of .25 on the xy plane

Some shared code between the traslate/rotate/scale branches of update_transform
was refactored into apply_transform so numeric transforms could reuse it.

This removes any "{X,Y,Z}-Axis Transform" messages. These prevented the
"Transforming: (x,y,z)" messages from showing, and the latter are more
useful, as they tell you the actual units.

This also rearranges finish_transform to clear _edit before updating
the axis rendering, so an axis doesn't remain highlighted.

Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
  • Loading branch information
Ryan Roden-Corrent and akien-mga committed Aug 10, 2023
1 parent faaf27f commit d6a83a6
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 114 deletions.
268 changes: 154 additions & 114 deletions editor/plugins/node_3d_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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.");
}
}

Expand All @@ -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;
}
Expand Down Expand Up @@ -4572,6 +4606,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);
Expand Down Expand Up @@ -4667,43 +4738,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: {
Expand Down Expand Up @@ -4773,38 +4812,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: {
Expand Down Expand Up @@ -4873,53 +4881,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);
Expand Down
11 changes: 11 additions & 0 deletions editor/plugins/node_3d_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,15 @@ class Node3DEditorViewport : public Control {
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 {
Expand Down Expand Up @@ -445,7 +454,9 @@ class Node3DEditorViewport : public Control {

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);
Expand Down

0 comments on commit d6a83a6

Please sign in to comment.