Skip to content

Commit da71bf8

Browse files
committed
Massively improve rotation gizmo ring raycasting
1 parent 506bacb commit da71bf8

File tree

1 file changed

+48
-14
lines changed

1 file changed

+48
-14
lines changed

editor/viewport/editor_transform_gizmo_4d.cpp

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ String EditorTransformGizmo4D::_get_transform_part_simple_action_name(const Tran
419419
return "Transform";
420420
}
421421

422-
Vector4 _origin_axis_aligned_biplane_raycast(const Vector4 &p_ray_origin, const Vector4 &p_ray_direction, const Vector4 &p_axis1, const Vector4 &p_axis2, const Vector4 &p_perp, const bool correct_for_ring) {
422+
Vector4 _origin_axis_aligned_biplane_raycast(const Vector4 &p_ray_origin, const Vector4 &p_ray_direction, const Vector4 &p_axis1, const Vector4 &p_axis2, const Vector4 &p_perp) {
423423
const Vector4 axis1_slid = Vector4D::slide(p_axis1, p_perp).normalized();
424424
if (axis1_slid == Vector4()) {
425425
return Vector4();
@@ -442,6 +442,40 @@ Vector4 _origin_axis_aligned_biplane_raycast(const Vector4 &p_ray_origin, const
442442
return Vector4();
443443
}
444444

445+
Vector4 _origin_axis_aligned_bivector_ring_raycast(const Vector4 &p_ray_origin, const Vector4 &p_ray_direction, const Vector4 &p_axis1, const Vector4 &p_axis2, const Vector4 &p_perp) {
446+
Vector4 plane_normal = Vector4D::perpendicular(p_axis1, p_axis2, p_perp);
447+
if (plane_normal.length_squared() < CMP_EPSILON) {
448+
return Vector4();
449+
}
450+
plane_normal = plane_normal.normalized();
451+
const real_t denominator = plane_normal.dot(p_ray_direction);
452+
if (Math::is_zero_approx(denominator)) {
453+
return Vector4();
454+
}
455+
const real_t factor = -plane_normal.dot(p_ray_origin) / denominator;
456+
if (factor < 0.0) {
457+
return Vector4(); // Gizmo is behind the camera.
458+
}
459+
const Vector4 point_on_plane = p_ray_origin + p_ray_direction * factor;
460+
// The visible ring is an ellipse due to foreshortening along p_perp.
461+
// To unsquash: solve the 2x2 Gram matrix system for the slid (projected) axes.
462+
// This handles cross-coupling when p_perp has components along both ring axes.
463+
const real_t axis1_dot_perp = p_axis1.dot(p_perp);
464+
const real_t axis2_dot_perp = p_axis2.dot(p_perp);
465+
const real_t det = 1.0 - axis1_dot_perp * axis1_dot_perp - axis2_dot_perp * axis2_dot_perp;
466+
if (Math::is_zero_approx(det)) {
467+
return Vector4(); // Ring is nearly edge-on to the camera.
468+
}
469+
// Project the hit point onto each slid axis to get its apparent (foreshortened) coordinates.
470+
const real_t point_on_plane_dot_perp = point_on_plane.dot(p_perp);
471+
const real_t apparent_axis1 = point_on_plane.dot(p_axis1) - point_on_plane_dot_perp * axis1_dot_perp;
472+
const real_t apparent_axis2 = point_on_plane.dot(p_axis2) - point_on_plane_dot_perp * axis2_dot_perp;
473+
// Solve the 2x2 system via Cramer's rule.
474+
const real_t proj_axis1 = (apparent_axis1 * (1.0 - axis2_dot_perp * axis2_dot_perp) + apparent_axis2 * axis1_dot_perp * axis2_dot_perp) / det;
475+
const real_t proj_axis2 = (apparent_axis2 * (1.0 - axis1_dot_perp * axis1_dot_perp) + apparent_axis1 * axis1_dot_perp * axis2_dot_perp) / det;
476+
return p_axis1 * proj_axis1 + p_axis2 * proj_axis2;
477+
}
478+
445479
EditorTransformGizmo4D::TransformPart EditorTransformGizmo4D::_check_for_best_hit(const Vector4 &p_local_ray_origin, const Vector4 &p_local_ray_direction, const Vector4 &p_local_perp_direction) const {
446480
Vector4 aligned;
447481
real_t current_distance;
@@ -497,7 +531,7 @@ EditorTransformGizmo4D::TransformPart EditorTransformGizmo4D::_check_for_best_hi
497531
// Check rotation rings.
498532
if (_is_rotation_enabled) {
499533
#define CHECK_RING(m_axis1, m_axis2, m_part) \
500-
current_point = _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, m_axis1, m_axis2, p_local_perp_direction, true); \
534+
current_point = _origin_axis_aligned_bivector_ring_raycast(p_local_ray_origin, p_local_ray_direction, m_axis1, m_axis2, p_local_perp_direction); \
501535
current_distance = Math::abs(current_point.length() - 1.0); \
502536
if (current_distance < closest_distance) { \
503537
closest_distance = current_distance; \
@@ -574,50 +608,50 @@ Variant EditorTransformGizmo4D::_get_transform_raycast_value(const Vector4 &p_lo
574608
} break;
575609
case TRANSFORM_MOVE_XY:
576610
case TRANSFORM_SCALE_XY: {
577-
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 1, 0, 0), p_local_perp_direction, false);
611+
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 1, 0, 0), p_local_perp_direction);
578612
} break;
579613
case TRANSFORM_MOVE_XZ:
580614
case TRANSFORM_SCALE_XZ: {
581-
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 0, 1, 0), p_local_perp_direction, false);
615+
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 0, 1, 0), p_local_perp_direction);
582616
} break;
583617
case TRANSFORM_MOVE_XW:
584618
case TRANSFORM_SCALE_XW: {
585-
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 0, 0, 1), p_local_perp_direction, false);
619+
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 0, 0, 1), p_local_perp_direction);
586620
} break;
587621
case TRANSFORM_MOVE_YZ:
588622
case TRANSFORM_SCALE_YZ: {
589-
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 1, 0, 0), Vector4(0, 0, 1, 0), p_local_perp_direction, false);
623+
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 1, 0, 0), Vector4(0, 0, 1, 0), p_local_perp_direction);
590624
} break;
591625
case TRANSFORM_MOVE_YW:
592626
case TRANSFORM_SCALE_YW: {
593-
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 1, 0, 0), Vector4(0, 0, 0, 1), p_local_perp_direction, false);
627+
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 1, 0, 0), Vector4(0, 0, 0, 1), p_local_perp_direction);
594628
} break;
595629
case TRANSFORM_MOVE_ZW:
596630
case TRANSFORM_SCALE_ZW: {
597-
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 0, 1, 0), Vector4(0, 0, 0, 1), p_local_perp_direction, false);
631+
return _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 0, 1, 0), Vector4(0, 0, 0, 1), p_local_perp_direction);
598632
} break;
599633
case TRANSFORM_ROTATE_XY: {
600-
Vector4 casted = _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 1, 0, 0), p_local_perp_direction, true);
634+
Vector4 casted = _origin_axis_aligned_bivector_ring_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 1, 0, 0), p_local_perp_direction);
601635
return Math::atan2(casted.y, casted.x);
602636
} break;
603637
case TRANSFORM_ROTATE_XZ: {
604-
Vector4 casted = _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 0, 1, 0), p_local_perp_direction, true);
638+
Vector4 casted = _origin_axis_aligned_bivector_ring_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 0, 1, 0), p_local_perp_direction);
605639
return Math::atan2(casted.x, casted.z);
606640
} break;
607641
case TRANSFORM_ROTATE_XW: {
608-
Vector4 casted = _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 0, 0, 1), p_local_perp_direction, true);
642+
Vector4 casted = _origin_axis_aligned_bivector_ring_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(1, 0, 0, 0), Vector4(0, 0, 0, 1), p_local_perp_direction);
609643
return Math::atan2(casted.w, casted.x);
610644
} break;
611645
case TRANSFORM_ROTATE_YZ: {
612-
Vector4 casted = _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 1, 0, 0), Vector4(0, 0, 1, 0), p_local_perp_direction, true);
646+
Vector4 casted = _origin_axis_aligned_bivector_ring_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 1, 0, 0), Vector4(0, 0, 1, 0), p_local_perp_direction);
613647
return Math::atan2(casted.z, casted.y);
614648
} break;
615649
case TRANSFORM_ROTATE_YW: {
616-
Vector4 casted = _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 1, 0, 0), Vector4(0, 0, 0, 1), p_local_perp_direction, true);
650+
Vector4 casted = _origin_axis_aligned_bivector_ring_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 1, 0, 0), Vector4(0, 0, 0, 1), p_local_perp_direction);
617651
return Math::atan2(casted.y, casted.w);
618652
} break;
619653
case TRANSFORM_ROTATE_ZW: {
620-
Vector4 casted = _origin_axis_aligned_biplane_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 0, 1, 0), Vector4(0, 0, 0, 1), p_local_perp_direction, true);
654+
Vector4 casted = _origin_axis_aligned_bivector_ring_raycast(p_local_ray_origin, p_local_ray_direction, Vector4(0, 0, 1, 0), Vector4(0, 0, 0, 1), p_local_perp_direction);
621655
return Math::atan2(casted.w, casted.z);
622656
} break;
623657
case TRANSFORM_MAX: {

0 commit comments

Comments
 (0)