Skip to content

Commit 12782ea

Browse files
committed
Add trackball-style rotation for 3D transform gizmo
1 parent 85c75da commit 12782ea

2 files changed

Lines changed: 145 additions & 6 deletions

File tree

editor/scene/3d/node_3d_editor_plugin.cpp

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ constexpr real_t GIZMO_VIEW_ROTATION_SIZE = 1.25;
116116
constexpr real_t GIZMO_SCALE_OFFSET = GIZMO_CIRCLE_SIZE + 0.3;
117117
constexpr real_t GIZMO_ARROW_OFFSET = GIZMO_CIRCLE_SIZE + 0.3;
118118

119+
constexpr real_t TRACKBALL_SENSITIVITY = 0.005;
120+
constexpr int TRACKBALL_SPHERE_RINGS = 16;
121+
constexpr int TRACKBALL_SPHERE_SECTORS = 32;
122+
constexpr real_t TRACKBALL_HIGHLIGHT_ALPHA = 0.01;
123+
constexpr int GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION = 15;
124+
constexpr int GIZMO_HIGHLIGHT_AXIS_TRACKBALL = 16;
125+
119126
constexpr real_t ZOOM_FREELOOK_MIN = 0.01;
120127
constexpr real_t ZOOM_FREELOOK_MULTIPLIER = 1.08;
121128
constexpr real_t ZOOM_FREELOOK_INDICATOR_DELAY_S = 1.5;
@@ -1400,6 +1407,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b
14001407
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
14011408
int col_axis = -1;
14021409
bool view_rotation_selected = false;
1410+
bool trackball_selected = false;
14031411

14041412
Vector3 hit_position;
14051413
Vector3 hit_normal;
@@ -1451,12 +1459,14 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b
14511459
if (Math::abs(distance_ray_to_center - view_rotation_radius) < circumference_tolerance &&
14521460
ray_length_to_center > 0) {
14531461
view_rotation_selected = true;
1462+
} else if (distance_ray_to_center < gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && ray_length_to_center > 0) {
1463+
trackball_selected = true;
14541464
}
14551465
}
14561466

14571467
if (view_rotation_selected) {
14581468
if (p_highlight_only) {
1459-
spatial_editor->select_gizmo_highlight_axis(15);
1469+
spatial_editor->select_gizmo_highlight_axis(GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION);
14601470
} else {
14611471
_edit.mode = TRANSFORM_ROTATE;
14621472
_compute_edit(p_screenpos);
@@ -1467,6 +1477,21 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b
14671477
_edit.gizmo_initiated = true;
14681478
}
14691479
return true;
1480+
} else if (trackball_selected) {
1481+
if (p_highlight_only) {
1482+
spatial_editor->select_gizmo_highlight_axis(GIZMO_HIGHLIGHT_AXIS_TRACKBALL);
1483+
} else {
1484+
_edit.mode = TRANSFORM_ROTATE;
1485+
_compute_edit(p_screenpos);
1486+
_edit.plane = TRANSFORM_VIEW;
1487+
_edit.is_trackball = true;
1488+
_edit.show_rotation_line = false;
1489+
_edit.accumulated_rotation_angle = 0.0;
1490+
_edit.rotation_axis = _get_camera_normal();
1491+
_edit.gizmo_initiated = true;
1492+
spatial_editor->select_gizmo_highlight_axis(-1);
1493+
}
1494+
return true;
14701495
} else if (col_axis != -1) {
14711496
if (p_highlight_only) {
14721497
spatial_editor->select_gizmo_highlight_axis(col_axis + 3);
@@ -3749,7 +3774,7 @@ void Node3DEditorViewport::_draw() {
37493774
break;
37503775
}
37513776

3752-
if (_is_rotation_arc_visible() && !_edit.initial_click_vector.is_zero_approx()) {
3777+
if (!_edit.is_trackball && _is_rotation_arc_visible() && !_edit.initial_click_vector.is_zero_approx()) {
37533778
Vector3 up = _edit.rotation_axis;
37543779
Vector3 right = _edit.initial_click_vector;
37553780

@@ -4456,6 +4481,16 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) {
44564481
RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
44574482
RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
44584483
}
4484+
4485+
// Create trackball sphere instance
4486+
trackball_sphere_instance = RS::get_singleton()->instance_create();
4487+
RS::get_singleton()->instance_set_base(trackball_sphere_instance, spatial_editor->get_trackball_sphere_gizmo()->get_rid());
4488+
RS::get_singleton()->instance_set_scenario(trackball_sphere_instance, get_tree()->get_root()->get_world_3d()->get_scenario());
4489+
RS::get_singleton()->instance_set_visible(trackball_sphere_instance, false);
4490+
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(trackball_sphere_instance, RS::SHADOW_CASTING_SETTING_OFF);
4491+
RS::get_singleton()->instance_set_layer_mask(trackball_sphere_instance, layer);
4492+
RS::get_singleton()->instance_geometry_set_flag(trackball_sphere_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4493+
RS::get_singleton()->instance_geometry_set_flag(trackball_sphere_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
44594494
}
44604495

44614496
void Node3DEditorViewport::_finish_gizmo_instances() {
@@ -4470,6 +4505,8 @@ void Node3DEditorViewport::_finish_gizmo_instances() {
44704505
}
44714506
// Rotation white outline
44724507
RS::get_singleton()->free_rid(rotate_gizmo_instance[3]);
4508+
4509+
RS::get_singleton()->free_rid(trackball_sphere_instance);
44734510
}
44744511

44754512
void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {
@@ -4608,6 +4645,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
46084645

46094646
bool keep_gizmo_visible = arc_visible && ((local_coords && (show_gizmo_flags & Node3DEditor::TRANSFORM_MODE_LOCAL)) || (!local_coords && (show_gizmo_flags & Node3DEditor::TRANSFORM_MODE_GLOBAL)));
46104647
bool hide_gizmo_during_rotation = arc_visible && !keep_gizmo_visible;
4648+
bool hide_gizmo_during_trackball = (_edit.mode == TRANSFORM_ROTATE && _edit.is_trackball);
46114649

46124650
int arc_replaces_ring = -1;
46134651
if (keep_gizmo_visible) {
@@ -4629,7 +4667,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
46294667
}
46304668
}
46314669

4632-
bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && !hide_gizmo_during_rotation;
4670+
bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && !hide_gizmo_during_rotation && !hide_gizmo_during_trackball;
46334671
bool show_rotate_gizmo = show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE);
46344672

46354673
for (int i = 0; i < 3; i++) {
@@ -4660,7 +4698,12 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
46604698
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], view_rotation_xform);
46614699
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], show_rotate_gizmo && arc_replaces_ring != 3);
46624700

4663-
bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE;
4701+
bool can_show_trackball = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && !hide_gizmo_during_rotation;
4702+
bool show_trackball_sphere = can_show_trackball && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) && !hide_gizmo_during_trackball;
4703+
RenderingServer::get_singleton()->instance_set_transform(trackball_sphere_instance, view_rotation_xform);
4704+
RenderingServer::get_singleton()->instance_set_visible(trackball_sphere_instance, show_trackball_sphere);
4705+
4706+
bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE && !hide_gizmo_during_trackball;
46644707
RenderingServer *rs = RenderingServer::get_singleton();
46654708
rs->instance_set_visible(axis_gizmo_instance[0], show_axes && (_edit.plane == TRANSFORM_X_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_XZ));
46664709
rs->instance_set_visible(axis_gizmo_instance[1], show_axes && (_edit.plane == TRANSFORM_Y_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_YZ));
@@ -5867,6 +5910,36 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
58675910
plane = Plane(_get_camera_normal(), _edit.center);
58685911
}
58695912

5913+
if (_edit.is_trackball) {
5914+
Vector2 motion_delta = _edit.mouse_pos - _edit.original_mouse_pos;
5915+
real_t sensitivity = TRACKBALL_SENSITIVITY * EDSCALE;
5916+
Vector2 rotation_input = motion_delta * sensitivity;
5917+
5918+
Transform3D cam_transform = to_camera_transform(cursor);
5919+
Vector3 cam_right = cam_transform.basis.get_column(0).normalized();
5920+
Vector3 cam_up = cam_transform.basis.get_column(1).normalized();
5921+
Vector3 rotation_axis = cam_up * rotation_input.x + cam_right * rotation_input.y;
5922+
5923+
real_t rotation_angle = rotation_axis.length();
5924+
if (rotation_angle > 0.0f) {
5925+
rotation_axis /= rotation_angle;
5926+
5927+
bool snapping = _edit.snap || spatial_editor->is_snap_enabled();
5928+
if (snapping) {
5929+
double snap_step = spatial_editor->get_rotate_snap();
5930+
double angle_deg = Math::rad_to_deg(rotation_angle);
5931+
angle_deg = Math::snapped(angle_deg, snap_step);
5932+
rotation_angle = Math::deg_to_rad(angle_deg);
5933+
}
5934+
5935+
double angle_deg = Math::rad_to_deg(rotation_angle);
5936+
set_message(vformat(TTR("Rotating %s degrees."), String::num(angle_deg, 2)));
5937+
5938+
apply_transform(rotation_axis, rotation_angle);
5939+
}
5940+
break;
5941+
}
5942+
58705943
Vector3 local_axis;
58715944
Vector3 global_axis;
58725945
switch (_edit.plane) {
@@ -6029,6 +6102,7 @@ void Node3DEditorViewport::finish_transform() {
60296102
_edit.numeric_input = 0;
60306103
_edit.numeric_next_decimal = 0;
60316104
_edit.numeric_negate = false;
6105+
_edit.is_trackball = false;
60326106
_edit.initial_click_vector = Vector3();
60336107
_edit.previous_rotation_vector = Vector3();
60346108
_edit.accumulated_rotation_angle = 0.0;
@@ -6840,12 +6914,15 @@ void Node3DEditor::select_gizmo_highlight_axis(int p_axis) {
68406914
for (int i = 0; i < 4; i++) {
68416915
bool highlight;
68426916
if (i == 3) {
6843-
highlight = (p_axis == 15);
6917+
highlight = (p_axis == GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION);
68446918
} else {
68456919
highlight = (i + 3) == p_axis;
68466920
}
68476921
rotate_gizmo[i]->surface_set_material(0, highlight ? rotate_gizmo_color_hl[i] : rotate_gizmo_color[i]);
68486922
}
6923+
6924+
bool highlight_trackball = (p_axis == GIZMO_HIGHLIGHT_AXIS_TRACKBALL);
6925+
trackball_sphere_gizmo->surface_set_material(0, highlight_trackball ? trackball_sphere_material_hl : trackball_sphere_material);
68496926
}
68506927

68516928
void Node3DEditor::update_transform_gizmo() {
@@ -8228,6 +8305,62 @@ void fragment() {
82288305
}
82298306
}
82308307

8308+
// Create trackball sphere
8309+
{
8310+
trackball_sphere_gizmo.instantiate();
8311+
Ref<SurfaceTool> surftool;
8312+
surftool.instantiate();
8313+
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
8314+
8315+
const int sphere_rings = TRACKBALL_SPHERE_RINGS;
8316+
const int sphere_sectors = TRACKBALL_SPHERE_SECTORS;
8317+
const real_t sphere_radius = GIZMO_CIRCLE_SIZE;
8318+
8319+
for (int r = 0; r <= sphere_rings; ++r) {
8320+
for (int s = 0; s <= sphere_sectors; ++s) {
8321+
real_t ring_angle = Math::PI * r / sphere_rings;
8322+
real_t sector_angle = 2.0 * Math::PI * s / sphere_sectors;
8323+
8324+
Vector3 vertex(
8325+
sphere_radius * Math::sin(ring_angle) * Math::cos(sector_angle),
8326+
sphere_radius * Math::cos(ring_angle),
8327+
sphere_radius * Math::sin(ring_angle) * Math::sin(sector_angle));
8328+
8329+
surftool->set_normal(vertex.normalized());
8330+
surftool->add_vertex(vertex);
8331+
}
8332+
}
8333+
8334+
for (int r = 0; r < sphere_rings; ++r) {
8335+
for (int s = 0; s < sphere_sectors; ++s) {
8336+
int current = r * (sphere_sectors + 1) + s;
8337+
int next = current + sphere_sectors + 1;
8338+
8339+
surftool->add_index(current);
8340+
surftool->add_index(next);
8341+
surftool->add_index(current + 1);
8342+
8343+
surftool->add_index(current + 1);
8344+
surftool->add_index(next);
8345+
surftool->add_index(next + 1);
8346+
}
8347+
}
8348+
8349+
trackball_sphere_material.instantiate();
8350+
trackball_sphere_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
8351+
trackball_sphere_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
8352+
trackball_sphere_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
8353+
trackball_sphere_material->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
8354+
trackball_sphere_material->set_albedo(Color(1.0, 1.0, 1.0, 0.0));
8355+
trackball_sphere_material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
8356+
8357+
trackball_sphere_material_hl = trackball_sphere_material->duplicate();
8358+
trackball_sphere_material_hl->set_albedo(Color(1.0, 1.0, 1.0, TRACKBALL_HIGHLIGHT_ALPHA));
8359+
8360+
surftool->set_material(trackball_sphere_material);
8361+
surftool->commit(trackball_sphere_gizmo);
8362+
}
8363+
82318364
_generate_selection_boxes();
82328365
}
82338366

editor/scene/3d/node_3d_editor_plugin.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ class Node3DEditorViewport : public Control {
383383
Point2 original_mouse_pos;
384384
bool snap = false;
385385
bool show_rotation_line = false;
386+
bool is_trackball = false;
386387
Ref<EditorNode3DGizmo> gizmo;
387388
int gizmo_handle = 0;
388389
bool gizmo_handle_secondary = false;
@@ -466,6 +467,7 @@ class Node3DEditorViewport : public Control {
466467
int zoom_failed_attempts_count = 0;
467468

468469
RID move_gizmo_instance[3], move_plane_gizmo_instance[3], rotate_gizmo_instance[4], scale_gizmo_instance[3], scale_plane_gizmo_instance[3], axis_gizmo_instance[3];
470+
RID trackball_sphere_instance;
469471

470472
String last_message;
471473
String message;
@@ -532,7 +534,7 @@ class Node3DEditorViewport : public Control {
532534

533535
void _project_settings_changed();
534536

535-
Transform3D _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 = false);
537+
Transform3D _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);
536538

537539
void _reset_transform(TransformType p_type);
538540

@@ -713,12 +715,15 @@ class Node3DEditor : public VBoxContainer {
713715
Vector3 grid_camera_last_update_position;
714716

715717
Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3], axis_gizmo[3];
718+
Ref<ArrayMesh> trackball_sphere_gizmo;
716719
Ref<StandardMaterial3D> gizmo_color[3];
717720
Ref<StandardMaterial3D> plane_gizmo_color[3];
718721
Ref<ShaderMaterial> rotate_gizmo_color[4];
719722
Ref<StandardMaterial3D> gizmo_color_hl[3];
720723
Ref<StandardMaterial3D> plane_gizmo_color_hl[3];
721724
Ref<ShaderMaterial> rotate_gizmo_color_hl[4];
725+
Ref<StandardMaterial3D> trackball_sphere_material;
726+
Ref<StandardMaterial3D> trackball_sphere_material_hl;
722727

723728
Ref<Node3DGizmo> current_hover_gizmo;
724729
int current_hover_gizmo_handle;
@@ -996,6 +1001,7 @@ class Node3DEditor : public VBoxContainer {
9961001
Ref<ArrayMesh> get_rotate_gizmo(int idx) const { return rotate_gizmo[idx]; }
9971002
Ref<ArrayMesh> get_scale_gizmo(int idx) const { return scale_gizmo[idx]; }
9981003
Ref<ArrayMesh> get_scale_plane_gizmo(int idx) const { return scale_plane_gizmo[idx]; }
1004+
Ref<ArrayMesh> get_trackball_sphere_gizmo() const { return trackball_sphere_gizmo; }
9991005

10001006
void update_grid();
10011007
void update_transform_gizmo();

0 commit comments

Comments
 (0)