Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement numeric blender-style transforms. #58389

Merged
merged 1 commit into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
KoBeWi marked this conversation as resolved.
Show resolved Hide resolved
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
Loading