Improve rotation in the 3D transform gizmo
* Get rid of deadzones. * Make it easier to select rotation handles at very oblique angles. * Handle rotation for axes that are perpendicular to the camera.
This commit is contained in:
parent
39562294ff
commit
8345aabaf4
2 changed files with 61 additions and 28 deletions
|
@ -1041,24 +1041,40 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b
|
|||
|
||||
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
|
||||
int col_axis = -1;
|
||||
float col_d = 1e20;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Plane plane(gt.basis.get_axis(i).normalized(), gt.origin);
|
||||
Vector3 r;
|
||||
if (!plane.intersects_ray(ray_pos, ray, &r)) {
|
||||
continue;
|
||||
Vector3 hit_position;
|
||||
Vector3 hit_normal;
|
||||
real_t ray_length = gt.origin.distance_to(ray_pos) + (GIZMO_CIRCLE_SIZE * gizmo_scale) * 4.0f;
|
||||
if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * ray_length, gt.origin, gizmo_scale * (GIZMO_CIRCLE_SIZE), &hit_position, &hit_normal)) {
|
||||
if (hit_normal.dot(_get_camera_normal()) < 0.05) {
|
||||
hit_position = gt.xform_inv(hit_position).abs();
|
||||
int min_axis = hit_position.min_axis_index();
|
||||
if (hit_position[min_axis] < gizmo_scale * GIZMO_RING_HALF_WIDTH) {
|
||||
col_axis = min_axis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const real_t dist = r.distance_to(gt.origin);
|
||||
const Vector3 r_dir = (r - gt.origin).normalized();
|
||||
if (col_axis == -1) {
|
||||
float col_d = 1e20;
|
||||
|
||||
if (_get_camera_normal().dot(r_dir) <= 0.005) {
|
||||
if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) {
|
||||
const real_t d = ray_pos.distance_to(r);
|
||||
if (d < col_d) {
|
||||
col_d = d;
|
||||
col_axis = i;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Plane plane(gt.basis.get_axis(i).normalized(), gt.origin);
|
||||
Vector3 r;
|
||||
if (!plane.intersects_ray(ray_pos, ray, &r)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const real_t dist = r.distance_to(gt.origin);
|
||||
const Vector3 r_dir = (r - gt.origin).normalized();
|
||||
|
||||
if (_get_camera_normal().dot(r_dir) <= 0.005) {
|
||||
if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) {
|
||||
const real_t d = ray_pos.distance_to(r);
|
||||
if (d < col_d) {
|
||||
col_d = d;
|
||||
col_axis = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2730,7 +2746,7 @@ void Node3DEditorViewport::_draw() {
|
|||
font->draw_string(ci, msgpos, message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1));
|
||||
}
|
||||
|
||||
if (_edit.mode == TRANSFORM_ROTATE) {
|
||||
if (_edit.mode == TRANSFORM_ROTATE && _edit.show_rotation_line) {
|
||||
Point2 center = _point_to_screen(_edit.center);
|
||||
|
||||
Color handle_color;
|
||||
|
@ -4278,24 +4294,23 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) {
|
|||
} break;
|
||||
|
||||
case TRANSFORM_ROTATE: {
|
||||
Plane plane;
|
||||
Vector3 axis;
|
||||
Plane plane = Plane(_get_camera_normal(), _edit.center);
|
||||
|
||||
Vector3 local_axis;
|
||||
Vector3 global_axis;
|
||||
switch (_edit.plane) {
|
||||
case TRANSFORM_VIEW:
|
||||
plane = Plane(_get_camera_normal(), _edit.center);
|
||||
// local_axis unused
|
||||
global_axis = _get_camera_normal();
|
||||
break;
|
||||
case TRANSFORM_X_AXIS:
|
||||
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0).normalized(), _edit.center);
|
||||
axis = Vector3(1, 0, 0);
|
||||
local_axis = Vector3(1, 0, 0);
|
||||
break;
|
||||
case TRANSFORM_Y_AXIS:
|
||||
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1).normalized(), _edit.center);
|
||||
axis = Vector3(0, 1, 0);
|
||||
local_axis = Vector3(0, 1, 0);
|
||||
break;
|
||||
case TRANSFORM_Z_AXIS:
|
||||
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2).normalized(), _edit.center);
|
||||
axis = Vector3(0, 0, 1);
|
||||
local_axis = Vector3(0, 0, 1);
|
||||
break;
|
||||
case TRANSFORM_YZ:
|
||||
case TRANSFORM_XZ:
|
||||
|
@ -4303,6 +4318,10 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (_edit.plane != TRANSFORM_VIEW) {
|
||||
global_axis = spatial_editor->get_gizmo_transform().basis.xform(local_axis).normalized();
|
||||
}
|
||||
|
||||
Vector3 intersection;
|
||||
if (!plane.intersects_ray(ray_pos, ray, &intersection)) {
|
||||
break;
|
||||
|
@ -4313,10 +4332,22 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) {
|
|||
break;
|
||||
}
|
||||
|
||||
Vector3 y_axis = (click - _edit.center).normalized();
|
||||
Vector3 x_axis = plane.normal.cross(y_axis).normalized();
|
||||
static const float orthogonal_threshold = Math::cos(Math::deg2rad(87.0f));
|
||||
bool axis_is_orthogonal = ABS(plane.normal.dot(global_axis)) < orthogonal_threshold;
|
||||
|
||||
double angle = Math::atan2(x_axis.dot(intersection - _edit.center), y_axis.dot(intersection - _edit.center));
|
||||
double angle = 0.0f;
|
||||
if (axis_is_orthogonal) {
|
||||
_edit.show_rotation_line = false;
|
||||
Vector3 projection_axis = plane.normal.cross(global_axis);
|
||||
Vector3 delta = intersection - click;
|
||||
float projection = delta.dot(projection_axis);
|
||||
angle = (projection * (Math_PI / 2.0f)) / (gizmo_scale * GIZMO_CIRCLE_SIZE);
|
||||
} else {
|
||||
_edit.show_rotation_line = true;
|
||||
Vector3 click_axis = (click - _edit.center).normalized();
|
||||
Vector3 current_axis = (intersection - _edit.center).normalized();
|
||||
angle = click_axis.signed_angle_to(current_axis, global_axis);
|
||||
}
|
||||
|
||||
if (_edit.snap || spatial_editor->is_snap_enabled()) {
|
||||
snap = spatial_editor->get_rotate_snap();
|
||||
|
@ -4344,7 +4375,7 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) {
|
|||
continue;
|
||||
}
|
||||
|
||||
Vector3 compute_axis = local_coords ? axis : plane.normal;
|
||||
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;
|
||||
|
@ -4377,6 +4408,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
|
|||
_edit.mode = TRANSFORM_NONE;
|
||||
_edit.plane = TRANSFORM_VIEW;
|
||||
_edit.snap = true;
|
||||
_edit.show_rotation_line = true;
|
||||
_edit.instant = false;
|
||||
_edit.gizmo_handle = -1;
|
||||
_edit.gizmo_handle_secondary = false;
|
||||
|
|
|
@ -311,6 +311,7 @@ private:
|
|||
Point2 mouse_pos;
|
||||
Point2 original_mouse_pos;
|
||||
bool snap = false;
|
||||
bool show_rotation_line = false;
|
||||
Ref<EditorNode3DGizmo> gizmo;
|
||||
int gizmo_handle = 0;
|
||||
bool gizmo_handle_secondary = false;
|
||||
|
|
Loading…
Reference in a new issue