Skip to content

Commit 2de3c4e

Browse files
committed
Add trackball-style rotation for 3D transform gizmo
1 parent 7716a4c commit 2de3c4e

File tree

2 files changed

+145
-6
lines changed

2 files changed

+145
-6
lines changed

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;
@@ -1383,6 +1390,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b
13831390
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
13841391
int col_axis = -1;
13851392
bool view_rotation_selected = false;
1393+
bool trackball_selected = false;
13861394

13871395
Vector3 hit_position;
13881396
Vector3 hit_normal;
@@ -1434,12 +1442,14 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b
14341442
if (Math::abs(distance_ray_to_center - view_rotation_radius) < circumference_tolerance &&
14351443
ray_length_to_center > 0) {
14361444
view_rotation_selected = true;
1445+
} else if (distance_ray_to_center < gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && ray_length_to_center > 0) {
1446+
trackball_selected = true;
14371447
}
14381448
}
14391449

14401450
if (view_rotation_selected) {
14411451
if (p_highlight_only) {
1442-
spatial_editor->select_gizmo_highlight_axis(15);
1452+
spatial_editor->select_gizmo_highlight_axis(GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION);
14431453
} else {
14441454
_edit.mode = TRANSFORM_ROTATE;
14451455
_compute_edit(p_screenpos);
@@ -1450,6 +1460,21 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b
14501460
_edit.gizmo_initiated = true;
14511461
}
14521462
return true;
1463+
} else if (trackball_selected) {
1464+
if (p_highlight_only) {
1465+
spatial_editor->select_gizmo_highlight_axis(GIZMO_HIGHLIGHT_AXIS_TRACKBALL);
1466+
} else {
1467+
_edit.mode = TRANSFORM_ROTATE;
1468+
_compute_edit(p_screenpos);
1469+
_edit.plane = TRANSFORM_VIEW;
1470+
_edit.is_trackball = true;
1471+
_edit.show_rotation_line = false;
1472+
_edit.accumulated_rotation_angle = 0.0;
1473+
_edit.rotation_axis = _get_camera_normal();
1474+
_edit.gizmo_initiated = true;
1475+
spatial_editor->select_gizmo_highlight_axis(-1);
1476+
}
1477+
return true;
14531478
} else if (col_axis != -1) {
14541479
if (p_highlight_only) {
14551480
spatial_editor->select_gizmo_highlight_axis(col_axis + 3);
@@ -3725,7 +3750,7 @@ void Node3DEditorViewport::_draw() {
37253750
break;
37263751
}
37273752

3728-
if (_is_rotation_arc_visible() && !_edit.initial_click_vector.is_zero_approx()) {
3753+
if (!_edit.is_trackball && _is_rotation_arc_visible() && !_edit.initial_click_vector.is_zero_approx()) {
37293754
Vector3 up = _edit.rotation_axis;
37303755
Vector3 right = _edit.initial_click_vector;
37313756

@@ -4432,6 +4457,16 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) {
44324457
RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
44334458
RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
44344459
}
4460+
4461+
// Create trackball sphere instance
4462+
trackball_sphere_instance = RS::get_singleton()->instance_create();
4463+
RS::get_singleton()->instance_set_base(trackball_sphere_instance, spatial_editor->get_trackball_sphere_gizmo()->get_rid());
4464+
RS::get_singleton()->instance_set_scenario(trackball_sphere_instance, get_tree()->get_root()->get_world_3d()->get_scenario());
4465+
RS::get_singleton()->instance_set_visible(trackball_sphere_instance, false);
4466+
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(trackball_sphere_instance, RS::SHADOW_CASTING_SETTING_OFF);
4467+
RS::get_singleton()->instance_set_layer_mask(trackball_sphere_instance, layer);
4468+
RS::get_singleton()->instance_geometry_set_flag(trackball_sphere_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4469+
RS::get_singleton()->instance_geometry_set_flag(trackball_sphere_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
44354470
}
44364471

44374472
void Node3DEditorViewport::_finish_gizmo_instances() {
@@ -4446,6 +4481,8 @@ void Node3DEditorViewport::_finish_gizmo_instances() {
44464481
}
44474482
// Rotation white outline
44484483
RS::get_singleton()->free_rid(rotate_gizmo_instance[3]);
4484+
4485+
RS::get_singleton()->free_rid(trackball_sphere_instance);
44494486
}
44504487

44514488
void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {
@@ -4580,8 +4617,9 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
45804617
}
45814618

45824619
bool hide_during_rotation = _is_rotation_arc_visible();
4620+
bool hide_during_trackball = (_edit.mode == TRANSFORM_ROTATE && _edit.is_trackball);
45834621

4584-
bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && !hide_during_rotation;
4622+
bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && !hide_during_rotation && !hide_during_trackball;
45854623
bool show_rotate_gizmo = show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE);
45864624

45874625
for (int i = 0; i < 3; i++) {
@@ -4610,7 +4648,12 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
46104648
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], view_rotation_xform);
46114649
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], show_rotate_gizmo);
46124650

4613-
bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE;
4651+
bool can_show_trackball = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && !hide_during_rotation;
4652+
bool show_trackball_sphere = can_show_trackball && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) && !hide_during_trackball;
4653+
RenderingServer::get_singleton()->instance_set_transform(trackball_sphere_instance, view_rotation_xform);
4654+
RenderingServer::get_singleton()->instance_set_visible(trackball_sphere_instance, show_trackball_sphere);
4655+
4656+
bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE && !hide_during_trackball;
46144657
RenderingServer *rs = RenderingServer::get_singleton();
46154658
rs->instance_set_visible(axis_gizmo_instance[0], show_axes && (_edit.plane == TRANSFORM_X_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_XZ));
46164659
rs->instance_set_visible(axis_gizmo_instance[1], show_axes && (_edit.plane == TRANSFORM_Y_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_YZ));
@@ -5817,6 +5860,36 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
58175860
plane = Plane(_get_camera_normal(), _edit.center);
58185861
}
58195862

5863+
if (_edit.is_trackball) {
5864+
Vector2 motion_delta = _edit.mouse_pos - _edit.original_mouse_pos;
5865+
real_t sensitivity = TRACKBALL_SENSITIVITY * EDSCALE;
5866+
Vector2 rotation_input = motion_delta * sensitivity;
5867+
5868+
Transform3D cam_transform = to_camera_transform(cursor);
5869+
Vector3 cam_right = cam_transform.basis.get_column(0).normalized();
5870+
Vector3 cam_up = cam_transform.basis.get_column(1).normalized();
5871+
Vector3 rotation_axis = cam_up * rotation_input.x + cam_right * rotation_input.y;
5872+
5873+
real_t rotation_angle = rotation_axis.length();
5874+
if (rotation_angle > 0.0f) {
5875+
rotation_axis /= rotation_angle;
5876+
5877+
bool snapping = _edit.snap || spatial_editor->is_snap_enabled();
5878+
if (snapping) {
5879+
double snap_step = spatial_editor->get_rotate_snap();
5880+
double angle_deg = Math::rad_to_deg(rotation_angle);
5881+
angle_deg = Math::snapped(angle_deg, snap_step);
5882+
rotation_angle = Math::deg_to_rad(angle_deg);
5883+
}
5884+
5885+
double angle_deg = Math::rad_to_deg(rotation_angle);
5886+
set_message(vformat(TTR("Rotating %s degrees."), String::num(angle_deg, 2)));
5887+
5888+
apply_transform(rotation_axis, rotation_angle);
5889+
}
5890+
break;
5891+
}
5892+
58205893
Vector3 local_axis;
58215894
Vector3 global_axis;
58225895
switch (_edit.plane) {
@@ -5973,6 +6046,7 @@ void Node3DEditorViewport::finish_transform() {
59736046
_edit.numeric_input = 0;
59746047
_edit.numeric_next_decimal = 0;
59756048
_edit.numeric_negate = false;
6049+
_edit.is_trackball = false;
59766050
_edit.initial_click_vector = Vector3();
59776051
_edit.previous_rotation_vector = Vector3();
59786052
_edit.accumulated_rotation_angle = 0.0;
@@ -6784,12 +6858,15 @@ void Node3DEditor::select_gizmo_highlight_axis(int p_axis) {
67846858
for (int i = 0; i < 4; i++) {
67856859
bool highlight;
67866860
if (i == 3) {
6787-
highlight = (p_axis == 15);
6861+
highlight = (p_axis == GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION);
67886862
} else {
67896863
highlight = (i + 3) == p_axis;
67906864
}
67916865
rotate_gizmo[i]->surface_set_material(0, highlight ? rotate_gizmo_color_hl[i] : rotate_gizmo_color[i]);
67926866
}
6867+
6868+
bool highlight_trackball = (p_axis == GIZMO_HIGHLIGHT_AXIS_TRACKBALL);
6869+
trackball_sphere_gizmo->surface_set_material(0, highlight_trackball ? trackball_sphere_material_hl : trackball_sphere_material);
67936870
}
67946871

67956872
void Node3DEditor::update_transform_gizmo() {
@@ -8166,6 +8243,62 @@ void fragment() {
81668243
}
81678244
}
81688245

8246+
// Create trackball sphere
8247+
{
8248+
trackball_sphere_gizmo.instantiate();
8249+
Ref<SurfaceTool> surftool;
8250+
surftool.instantiate();
8251+
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
8252+
8253+
const int sphere_rings = TRACKBALL_SPHERE_RINGS;
8254+
const int sphere_sectors = TRACKBALL_SPHERE_SECTORS;
8255+
const real_t sphere_radius = GIZMO_CIRCLE_SIZE;
8256+
8257+
for (int r = 0; r <= sphere_rings; ++r) {
8258+
for (int s = 0; s <= sphere_sectors; ++s) {
8259+
real_t ring_angle = Math::PI * r / sphere_rings;
8260+
real_t sector_angle = 2.0 * Math::PI * s / sphere_sectors;
8261+
8262+
Vector3 vertex(
8263+
sphere_radius * Math::sin(ring_angle) * Math::cos(sector_angle),
8264+
sphere_radius * Math::cos(ring_angle),
8265+
sphere_radius * Math::sin(ring_angle) * Math::sin(sector_angle));
8266+
8267+
surftool->set_normal(vertex.normalized());
8268+
surftool->add_vertex(vertex);
8269+
}
8270+
}
8271+
8272+
for (int r = 0; r < sphere_rings; ++r) {
8273+
for (int s = 0; s < sphere_sectors; ++s) {
8274+
int current = r * (sphere_sectors + 1) + s;
8275+
int next = current + sphere_sectors + 1;
8276+
8277+
surftool->add_index(current);
8278+
surftool->add_index(next);
8279+
surftool->add_index(current + 1);
8280+
8281+
surftool->add_index(current + 1);
8282+
surftool->add_index(next);
8283+
surftool->add_index(next + 1);
8284+
}
8285+
}
8286+
8287+
trackball_sphere_material.instantiate();
8288+
trackball_sphere_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
8289+
trackball_sphere_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
8290+
trackball_sphere_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
8291+
trackball_sphere_material->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
8292+
trackball_sphere_material->set_albedo(Color(1.0, 1.0, 1.0, 0.0));
8293+
trackball_sphere_material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
8294+
8295+
trackball_sphere_material_hl = trackball_sphere_material->duplicate();
8296+
trackball_sphere_material_hl->set_albedo(Color(1.0, 1.0, 1.0, TRACKBALL_HIGHLIGHT_ALPHA));
8297+
8298+
surftool->set_material(trackball_sphere_material);
8299+
surftool->commit(trackball_sphere_gizmo);
8300+
}
8301+
81698302
_generate_selection_boxes();
81708303
}
81718304

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

@@ -709,12 +711,15 @@ class Node3DEditor : public VBoxContainer {
709711
Vector3 grid_camera_last_update_position;
710712

711713
Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3], axis_gizmo[3];
714+
Ref<ArrayMesh> trackball_sphere_gizmo;
712715
Ref<StandardMaterial3D> gizmo_color[3];
713716
Ref<StandardMaterial3D> plane_gizmo_color[3];
714717
Ref<ShaderMaterial> rotate_gizmo_color[4];
715718
Ref<StandardMaterial3D> gizmo_color_hl[3];
716719
Ref<StandardMaterial3D> plane_gizmo_color_hl[3];
717720
Ref<ShaderMaterial> rotate_gizmo_color_hl[4];
721+
Ref<StandardMaterial3D> trackball_sphere_material;
722+
Ref<StandardMaterial3D> trackball_sphere_material_hl;
718723

719724
Ref<Node3DGizmo> current_hover_gizmo;
720725
int current_hover_gizmo_handle;
@@ -992,6 +997,7 @@ class Node3DEditor : public VBoxContainer {
992997
Ref<ArrayMesh> get_rotate_gizmo(int idx) const { return rotate_gizmo[idx]; }
993998
Ref<ArrayMesh> get_scale_gizmo(int idx) const { return scale_gizmo[idx]; }
994999
Ref<ArrayMesh> get_scale_plane_gizmo(int idx) const { return scale_plane_gizmo[idx]; }
1000+
Ref<ArrayMesh> get_trackball_sphere_gizmo() const { return trackball_sphere_gizmo; }
9951001

9961002
void update_grid();
9971003
void update_transform_gizmo();

0 commit comments

Comments
 (0)