Gizmo25D.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. using Godot;
  2. // This is identical to the GDScript version, yet it doesn't work.
  3. [Tool]
  4. public partial class Gizmo25D : Node2D
  5. {
  6. // Not pixel perfect for all axes in all modes, but works well enough.
  7. // Rounding is not done until after the movement is finished.
  8. private const bool RoughlyRoundToPixels = true;
  9. // Set when the node is created.
  10. public Node25D node25d;
  11. public Node3D spatialNode;
  12. // Input from Viewport25D, represents if the mouse is clicked.
  13. public bool wantsToMove = false;
  14. // Used to control the state of movement.
  15. private bool _moving = false;
  16. private Vector2 _startPosition = Vector2.Zero;
  17. // Stores state of closest or currently used axis.
  18. private int dominantAxis;
  19. private Node2D linesRoot;
  20. private Line2D[] lines = new Line2D[3];
  21. public override void _Ready()
  22. {
  23. linesRoot = GetChild<Node2D>(0);
  24. lines[0] = linesRoot.GetChild<Line2D>(0);
  25. lines[1] = linesRoot.GetChild<Line2D>(1);
  26. lines[2] = linesRoot.GetChild<Line2D>(2);
  27. }
  28. public override void _Process(float delta)
  29. {
  30. if (lines == null)
  31. {
  32. return; // Somehow this node hasn't been set up yet.
  33. }
  34. if (node25d == null)
  35. {
  36. return; // We're most likely viewing the Gizmo25D scene.
  37. }
  38. // While getting the mouse position works in any viewport, it doesn't do
  39. // anything significant unless the mouse is in the 2.5D viewport.
  40. Vector2 mousePosition = GetLocalMousePosition();
  41. if (!_moving)
  42. {
  43. // If the mouse is farther than this many pixels, it won't grab anything.
  44. float closestDistance = 20.0f;
  45. dominantAxis = -1;
  46. for (int i = 0; i < 3; i++)
  47. {
  48. // Unrelated, but needs a loop too.
  49. Color modulateLine = lines[i].Modulate;
  50. modulateLine.a = 0.8f;
  51. lines[i].Modulate = modulateLine;
  52. var distance = DistanceToSegmentAtIndex(i, mousePosition);
  53. if (distance < closestDistance)
  54. {
  55. closestDistance = distance;
  56. dominantAxis = i;
  57. }
  58. }
  59. if (dominantAxis == -1)
  60. {
  61. // If we're not hovering over a line, ensure they are placed correctly.
  62. linesRoot.GlobalPosition = node25d.GlobalPosition;
  63. return;
  64. }
  65. }
  66. Color modulate = lines[dominantAxis].Modulate;
  67. modulate.a = 1;
  68. lines[dominantAxis].Modulate = modulate;
  69. if (!wantsToMove)
  70. {
  71. _moving = false;
  72. }
  73. else if (wantsToMove && !_moving)
  74. {
  75. _moving = true;
  76. _startPosition = mousePosition;
  77. }
  78. if (_moving)
  79. {
  80. // Change modulate of unselected axes.
  81. modulate = lines[(dominantAxis + 1) % 3].Modulate;
  82. modulate.a = 0.5f;
  83. lines[(dominantAxis + 1) % 3].Modulate = modulate;
  84. lines[(dominantAxis + 2) % 3].Modulate = modulate;
  85. // Calculate mouse movement and reset for next frame.
  86. var mouseDiff = mousePosition - _startPosition;
  87. _startPosition = mousePosition;
  88. // Calculate movement.
  89. var projectedDiff = mouseDiff.Project(lines[dominantAxis].Points[1]);
  90. var movement = projectedDiff.Length() / Node25D.SCALE;
  91. if (Mathf.IsEqualApprox(Mathf.Pi, projectedDiff.AngleTo(lines[dominantAxis].Points[1])))
  92. {
  93. movement *= -1;
  94. }
  95. // Apply movement.
  96. Transform3D t = spatialNode.Transform;
  97. t.origin += t.basis[dominantAxis] * movement;
  98. spatialNode.Transform = t;
  99. }
  100. else
  101. {
  102. // Make sure the gizmo is located at the object.
  103. GlobalPosition = node25d.GlobalPosition;
  104. if (RoughlyRoundToPixels)
  105. {
  106. Transform3D t = spatialNode.Transform;
  107. t.origin = (t.origin * Node25D.SCALE).Round() / Node25D.SCALE;
  108. spatialNode.Transform = t;
  109. }
  110. }
  111. // Move the gizmo lines appropriately.
  112. linesRoot.GlobalPosition = node25d.GlobalPosition;
  113. node25d.PropertyListChangedNotify();
  114. }
  115. // Initializes after _ready due to the onready vars, called manually in Viewport25D.gd.
  116. // Sets up the points based on the basis values of the Node25D.
  117. public void Initialize()
  118. {
  119. var basis = node25d.Basis25D;
  120. for (int i = 0; i < 3; i++)
  121. {
  122. lines[i].Points[1] = basis[i] * 3;
  123. }
  124. GlobalPosition = node25d.GlobalPosition;
  125. spatialNode = node25d.GetChild<Node3D>(0);
  126. }
  127. // Figures out if the mouse is very close to a segment. This method is
  128. // specialized for this script, it assumes that each segment starts at
  129. // (0, 0) and it provides a deadzone around the origin.
  130. private float DistanceToSegmentAtIndex(int index, Vector2 point)
  131. {
  132. if (lines == null)
  133. {
  134. return Mathf.Inf;
  135. }
  136. if (point.LengthSquared() < 400)
  137. {
  138. return Mathf.Inf;
  139. }
  140. Vector2 segmentEnd = lines[index].Points[1];
  141. float lengthSquared = segmentEnd.LengthSquared();
  142. if (lengthSquared < 400)
  143. {
  144. return Mathf.Inf;
  145. }
  146. var t = Mathf.Clamp(point.Dot(segmentEnd) / lengthSquared, 0, 1);
  147. var projection = t * segmentEnd;
  148. return point.DistanceTo(projection);
  149. }
  150. }