TrackBallController.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /*
  2. Copyright (c) 2018, Lars Brubaker, John Lewin
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are met:
  6. 1. Redistributions of source code must retain the above copyright notice, this
  7. list of conditions and the following disclaimer.
  8. 2. Redistributions in binary form must reproduce the above copyright notice,
  9. this list of conditions and the following disclaimer in the documentation
  10. and/or other materials provided with the distribution.
  11. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  12. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  13. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  14. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  15. ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  16. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  17. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  18. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  19. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  20. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  21. The views and conclusions contained in the software and documentation are those
  22. of the authors and should not be interpreted as representing official policies,
  23. either expressed or implied, of the FreeBSD Project.
  24. */
  25. using System;
  26. namespace MatterHackers.VectorMath.TrackBall
  27. {
  28. public enum TrackBallTransformType { None, Translation, Rotation, Scale };
  29. public class TrackBallController
  30. {
  31. private Matrix4X4 localToScreenTransform;
  32. private Vector2 mouseDownPosition;
  33. public Vector2 ScreenCenter { get; set; }
  34. private Vector2 lastTranslationMousePosition = Vector2.Zero;
  35. private Vector2 lastScaleMousePosition = Vector2.Zero;
  36. private readonly WorldView world;
  37. public TrackBallController(WorldView world, double trackBallRadius = 1)
  38. {
  39. mouseDownPosition = new Vector2();
  40. this.TrackBallRadius = trackBallRadius;
  41. this.world = world;
  42. }
  43. public TrackBallTransformType CurrentTrackingType { get; private set; } = TrackBallTransformType.None;
  44. public static Vector3 MapMoveToSphere(Vector2 screenCenter, double trackBallRadius, Vector2 startPosition, Vector2 endPosition, bool rotateOnScreenZ)
  45. {
  46. if (rotateOnScreenZ)
  47. {
  48. var angleToTravel = screenCenter.GetDeltaAngle(startPosition, endPosition);
  49. // now rotate that position about z in the direction of the screen vector
  50. var positionOnRotationSphere = Vector3Ex.Transform(new Vector3(1, 0, 0), Matrix4X4.CreateRotationZ(angleToTravel/2));
  51. return positionOnRotationSphere;
  52. }
  53. else
  54. {
  55. var deltaFromStartPixels = endPosition - startPosition;
  56. var deltaOnSurface = new Vector2(deltaFromStartPixels.X / trackBallRadius, deltaFromStartPixels.Y / trackBallRadius);
  57. var lengthOnSurfaceRadi = deltaOnSurface.Length;
  58. // get this rotation on the surface of the sphere about y
  59. var positionAboutY = Vector3Ex.Transform(new Vector3(0, 0, 1), Matrix4X4.CreateRotationY(lengthOnSurfaceRadi));
  60. // get the angle that this distance travels around the sphere
  61. var angleToTravel = Math.Atan2(deltaOnSurface.Y, deltaOnSurface.X);
  62. // now rotate that position about z in the direction of the screen vector
  63. var positionOnRotationSphere = Vector3Ex.Transform(positionAboutY, Matrix4X4.CreateRotationZ(angleToTravel));
  64. return positionOnRotationSphere;
  65. }
  66. }
  67. //Mouse down
  68. public void OnMouseDown(Vector2 mousePosition, Matrix4X4 screenToLocal, TrackBallTransformType trackType = TrackBallTransformType.Rotation)
  69. {
  70. //if (currentTrackingType == MouseDownType.None)
  71. {
  72. CurrentTrackingType = trackType;
  73. switch (CurrentTrackingType)
  74. {
  75. case TrackBallTransformType.Rotation:
  76. mouseDownPosition = mousePosition;
  77. break;
  78. case TrackBallTransformType.Translation:
  79. localToScreenTransform = Matrix4X4.Invert(screenToLocal);
  80. lastTranslationMousePosition = mousePosition;
  81. break;
  82. case TrackBallTransformType.Scale:
  83. lastScaleMousePosition = mousePosition;
  84. break;
  85. default:
  86. throw new NotImplementedException();
  87. }
  88. }
  89. }
  90. //Mouse drag, calculate rotation
  91. public void OnMouseMove(Vector2 mousePosition)
  92. {
  93. switch (CurrentTrackingType)
  94. {
  95. case TrackBallTransformType.Rotation:
  96. Quaternion activeRotationQuaternion = GetRotationForMove(ScreenCenter, TrackBallRadius, mouseDownPosition, mousePosition, false);
  97. if (activeRotationQuaternion != Quaternion.Identity)
  98. {
  99. mouseDownPosition = mousePosition;
  100. world.RotationMatrix *= Matrix4X4.CreateRotation(activeRotationQuaternion);
  101. }
  102. break;
  103. case TrackBallTransformType.Translation:
  104. {
  105. Vector2 mouseDelta = mousePosition - lastTranslationMousePosition;
  106. Vector2 scaledDelta = mouseDelta / this.ScreenCenter.X * 4.75;
  107. var offset = new Vector3(scaledDelta.X, scaledDelta.Y, 0);
  108. offset = Vector3Ex.TransformPosition(offset, Matrix4X4.Invert(world.RotationMatrix));
  109. offset = Vector3Ex.TransformPosition(offset, localToScreenTransform);
  110. world.TranslationMatrix *= Matrix4X4.CreateTranslation(offset);
  111. lastTranslationMousePosition = mousePosition;
  112. }
  113. break;
  114. case TrackBallTransformType.Scale:
  115. {
  116. Vector2 mouseDelta = mousePosition - lastScaleMousePosition;
  117. double zoomDelta = 1;
  118. if (mouseDelta.Y < 0)
  119. {
  120. zoomDelta = 1 - (-1 * mouseDelta.Y / 100);
  121. }
  122. else if (mouseDelta.Y > 0)
  123. {
  124. zoomDelta = 1 + (1 * mouseDelta.Y / 100);
  125. }
  126. world.Scale *= zoomDelta;
  127. lastScaleMousePosition = mousePosition;
  128. }
  129. break;
  130. default:
  131. throw new NotImplementedException();
  132. }
  133. }
  134. public static Quaternion GetRotationForMove(Vector2 screenCenter, double trackBallRadius, Vector2 startPosition, Vector2 endPosition, bool rotateOnZ)
  135. {
  136. var activeRotationQuaternion = Quaternion.Identity;
  137. //Map the point to the sphere
  138. var moveSpherePosition = MapMoveToSphere(screenCenter, trackBallRadius, startPosition, endPosition, rotateOnZ);
  139. //Return the quaternion equivalent to the rotation
  140. //Compute the vector perpendicular to the begin and end vectors
  141. var rotationStart3D = new Vector3(0, 0, 1);
  142. if (rotateOnZ)
  143. {
  144. rotationStart3D = new Vector3(1, 0, 0);
  145. }
  146. Vector3 perp = rotationStart3D.Cross(moveSpherePosition);
  147. //Compute the length of the perpendicular vector
  148. double epsilon = 1.0e-5;
  149. if (perp.Length > epsilon)
  150. {
  151. //if its non-zero
  152. //We're ok, so return the perpendicular vector as the transform after all
  153. activeRotationQuaternion.X = perp.X;
  154. activeRotationQuaternion.Y = perp.Y;
  155. activeRotationQuaternion.Z = perp.Z;
  156. //In the quaternion values, w is cosine (theta / 2), where theta is the rotation angle
  157. activeRotationQuaternion.W = -rotationStart3D.Dot(moveSpherePosition);
  158. }
  159. return activeRotationQuaternion;
  160. }
  161. public void OnMouseUp()
  162. {
  163. switch (CurrentTrackingType)
  164. {
  165. case TrackBallTransformType.Rotation:
  166. break;
  167. case TrackBallTransformType.Translation:
  168. //currentTranslationMatrix = Matrix4X4.Identity;
  169. break;
  170. case TrackBallTransformType.Scale:
  171. break;
  172. default:
  173. throw new NotImplementedException();
  174. }
  175. CurrentTrackingType = TrackBallTransformType.None;
  176. }
  177. public void OnMouseWheel(int wheelDelta)
  178. {
  179. double zoomDelta = 1;
  180. if (wheelDelta > 0)
  181. {
  182. zoomDelta = 1.2;
  183. }
  184. else if (wheelDelta < 0)
  185. {
  186. zoomDelta = .8;
  187. }
  188. world.Scale *= zoomDelta;
  189. }
  190. public double TrackBallRadius { get; set; }
  191. }
  192. }