gizmo_25d.gd 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. @tool
  2. extends Node2D
  3. # Not pixel perfect for all axes in all modes, but works well enough.
  4. # Rounding is not done until after the movement is finished.
  5. const ROUGHLY_ROUND_TO_PIXELS = true
  6. # Set when the node is created.
  7. var node_25d: Node25D
  8. var spatial_node
  9. # Input from Viewport25D, represents if the mouse is clicked.
  10. var wants_to_move = false
  11. # Used to control the state of movement.
  12. var _moving = false
  13. var _start_position = Vector2()
  14. # Stores state of closest or currently used axis.
  15. var dominant_axis
  16. @onready var lines_root = $Lines
  17. @onready var lines = [$Lines/X, $Lines/Y, $Lines/Z]
  18. func _process(_delta):
  19. if not lines:
  20. return # Somehow this node hasn't been set up yet.
  21. if not node_25d:
  22. return # We're most likely viewing the Gizmo25D scene.
  23. # While getting the mouse position works in any viewport, it doesn't do
  24. # anything significant unless the mouse is in the 2.5D viewport.
  25. var mouse_position = get_local_mouse_position()
  26. if not _moving:
  27. # If the mouse is farther than this many pixels, it won't grab anything.
  28. var closest_distance = 20.0
  29. dominant_axis = -1
  30. for i in range(3):
  31. lines[i].modulate.a = 0.8 # Unrelated, but needs a loop too.
  32. var distance = _distance_to_segment_at_index(i, mouse_position)
  33. if distance < closest_distance:
  34. closest_distance = distance
  35. dominant_axis = i
  36. if dominant_axis == -1:
  37. # If we're not hovering over a line, ensure they are placed correctly.
  38. lines_root.global_position = node_25d.global_position
  39. return
  40. lines[dominant_axis].modulate.a = 1
  41. if not wants_to_move:
  42. _moving = false
  43. elif wants_to_move and not _moving:
  44. _moving = true
  45. _start_position = mouse_position
  46. if _moving:
  47. # Change modulate of unselected axes.
  48. lines[(dominant_axis + 1) % 3].modulate.a = 0.5
  49. lines[(dominant_axis + 2) % 3].modulate.a = 0.5
  50. # Calculate mouse movement and reset for next frame.
  51. var mouse_diff = mouse_position - _start_position
  52. _start_position = mouse_position
  53. # Calculate movement.
  54. var projected_diff = mouse_diff.project(lines[dominant_axis].points[1])
  55. var movement = projected_diff.length() / Node25D.SCALE
  56. if is_equal_approx(PI, projected_diff.angle_to(lines[dominant_axis].points[1])):
  57. movement *= -1
  58. # Apply movement.
  59. spatial_node.transform.origin += spatial_node.transform.basis[dominant_axis] * movement
  60. else:
  61. # Make sure the gizmo is located at the object.
  62. global_position = node_25d.global_position
  63. if ROUGHLY_ROUND_TO_PIXELS:
  64. spatial_node.transform.origin = (spatial_node.transform.origin * Node25D.SCALE).round() / Node25D.SCALE
  65. # Move the gizmo lines appropriately.
  66. lines_root.global_position = node_25d.global_position
  67. node_25d.property_list_changed_notify()
  68. # Initializes after _ready due to the onready vars, called manually in Viewport25D.gd.
  69. # Sets up the points based on the basis values of the Node25D.
  70. func initialize():
  71. var basis = node_25d.get_basis()
  72. for i in range(3):
  73. lines[i].points[1] = basis[i] * 3
  74. global_position = node_25d.global_position
  75. spatial_node = node_25d.get_child(0)
  76. # Figures out if the mouse is very close to a segment. This method is
  77. # specialized for this script, it assumes that each segment starts at
  78. # (0, 0) and it provides a deadzone around the origin.
  79. func _distance_to_segment_at_index(index, point):
  80. if not lines:
  81. return INF
  82. if point.length_squared() < 400:
  83. return INF
  84. var segment_end = lines[index].points[1]
  85. var length_squared = segment_end.length_squared()
  86. if length_squared < 400:
  87. return INF
  88. var t = clamp(point.dot(segment_end) / length_squared, 0, 1)
  89. var projection = t * segment_end
  90. return point.distance_to(projection)