@@ -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+
445479EditorTransformGizmo4D::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