Assignment for RMIT Mixed Reality in 2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
12 KiB

  1. // Knob|Controls3D|100060
  2. namespace VRTK
  3. {
  4. using UnityEngine;
  5. /// <summary>
  6. /// Attaching the script to a game object will allow the user to interact with it as if it were a radial knob. The direction can be freely set.
  7. /// </summary>
  8. /// <remarks>
  9. /// The script will instantiate the required Rigidbody and Interactable components automatically in case they do not exist yet.
  10. /// </remarks>
  11. /// <example>
  12. /// `VRTK/Examples/025_Controls_Overview` has a couple of rotator knobs that can be rotated by grabbing with the controller and then rotating the controller in the desired direction.
  13. /// </example>
  14. [AddComponentMenu("VRTK/Scripts/Controls/3D/VRTK_Knob")]
  15. [System.Obsolete("`VRTK.VRTK_Knob` has been deprecated and can be recreated with `VRTK.Controllables.PhysicsBased.VRTK_PhysicsRotator`. This script will be removed in a future version of VRTK.")]
  16. public class VRTK_Knob : VRTK_Control
  17. {
  18. /// <summary>
  19. /// The direction of the knob.
  20. /// </summary>
  21. public enum KnobDirection
  22. {
  23. /// <summary>
  24. /// The world x direction.
  25. /// </summary>
  26. x,
  27. /// <summary>
  28. /// The world y direction.
  29. /// </summary>
  30. y,
  31. /// <summary>
  32. /// The world z direction.
  33. /// </summary>
  34. z
  35. }
  36. [Tooltip("An optional game object to which the knob will be connected. If the game object moves the knob will follow along.")]
  37. public GameObject connectedTo;
  38. [Tooltip("The axis on which the knob should rotate. All other axis will be frozen.")]
  39. public KnobDirection direction = KnobDirection.x;
  40. [Tooltip("The minimum value of the knob.")]
  41. public float min = 0f;
  42. [Tooltip("The maximum value of the knob.")]
  43. public float max = 100f;
  44. [Tooltip("The increments in which knob values can change.")]
  45. public float stepSize = 1f;
  46. protected const float MAX_AUTODETECT_KNOB_WIDTH = 3; // multiple of the knob width
  47. protected KnobDirection finalDirection;
  48. protected KnobDirection subDirection;
  49. protected bool subDirectionFound = false;
  50. protected Quaternion initialRotation;
  51. protected Vector3 initialLocalRotation;
  52. protected ConfigurableJoint knobJoint;
  53. protected bool knobJointCreated = false;
  54. protected override void InitRequiredComponents()
  55. {
  56. initialRotation = transform.rotation;
  57. initialLocalRotation = transform.localRotation.eulerAngles;
  58. InitKnob();
  59. }
  60. protected override bool DetectSetup()
  61. {
  62. finalDirection = direction;
  63. if (knobJointCreated)
  64. {
  65. knobJoint.angularXMotion = ConfigurableJointMotion.Locked;
  66. knobJoint.angularYMotion = ConfigurableJointMotion.Locked;
  67. knobJoint.angularZMotion = ConfigurableJointMotion.Locked;
  68. switch (finalDirection)
  69. {
  70. case KnobDirection.x:
  71. knobJoint.angularXMotion = ConfigurableJointMotion.Free;
  72. break;
  73. case KnobDirection.y:
  74. knobJoint.angularYMotion = ConfigurableJointMotion.Free;
  75. break;
  76. case KnobDirection.z:
  77. knobJoint.angularZMotion = ConfigurableJointMotion.Free;
  78. break;
  79. }
  80. }
  81. if (knobJoint)
  82. {
  83. knobJoint.xMotion = ConfigurableJointMotion.Locked;
  84. knobJoint.yMotion = ConfigurableJointMotion.Locked;
  85. knobJoint.zMotion = ConfigurableJointMotion.Locked;
  86. if (connectedTo)
  87. {
  88. knobJoint.connectedBody = connectedTo.GetComponent<Rigidbody>();
  89. }
  90. }
  91. return true;
  92. }
  93. protected override ControlValueRange RegisterValueRange()
  94. {
  95. return new ControlValueRange()
  96. {
  97. controlMin = min,
  98. controlMax = max
  99. };
  100. }
  101. protected override void HandleUpdate()
  102. {
  103. value = CalculateValue();
  104. }
  105. protected virtual void InitKnob()
  106. {
  107. Rigidbody knobRigidbody = GetComponent<Rigidbody>();
  108. if (knobRigidbody == null)
  109. {
  110. knobRigidbody = gameObject.AddComponent<Rigidbody>();
  111. knobRigidbody.angularDrag = 10; // otherwise knob will continue to move too far on its own
  112. }
  113. knobRigidbody.isKinematic = false;
  114. knobRigidbody.useGravity = false;
  115. VRTK_InteractableObject knobInteractableObject = GetComponent<VRTK_InteractableObject>();
  116. if (knobInteractableObject == null)
  117. {
  118. knobInteractableObject = gameObject.AddComponent<VRTK_InteractableObject>();
  119. }
  120. knobInteractableObject.isGrabbable = true;
  121. knobInteractableObject.grabAttachMechanicScript = gameObject.AddComponent<GrabAttachMechanics.VRTK_TrackObjectGrabAttach>();
  122. knobInteractableObject.grabAttachMechanicScript.precisionGrab = true;
  123. knobInteractableObject.secondaryGrabActionScript = gameObject.AddComponent<SecondaryControllerGrabActions.VRTK_SwapControllerGrabAction>();
  124. knobInteractableObject.stayGrabbedOnTeleport = false;
  125. knobJoint = GetComponent<ConfigurableJoint>();
  126. if (knobJoint == null)
  127. {
  128. knobJoint = gameObject.AddComponent<ConfigurableJoint>();
  129. knobJoint.configuredInWorldSpace = false;
  130. knobJointCreated = true;
  131. }
  132. if (connectedTo)
  133. {
  134. Rigidbody knobConnectedToRigidbody = connectedTo.GetComponent<Rigidbody>();
  135. if (knobConnectedToRigidbody == null)
  136. {
  137. knobConnectedToRigidbody = connectedTo.AddComponent<Rigidbody>();
  138. knobConnectedToRigidbody.useGravity = false;
  139. knobConnectedToRigidbody.isKinematic = true;
  140. }
  141. }
  142. }
  143. protected virtual KnobDirection DetectDirection()
  144. {
  145. KnobDirection returnDirection = KnobDirection.x;
  146. Bounds bounds = VRTK_SharedMethods.GetBounds(transform);
  147. // shoot rays in all directions to learn about surroundings
  148. RaycastHit hitForward;
  149. RaycastHit hitBack;
  150. RaycastHit hitLeft;
  151. RaycastHit hitRight;
  152. RaycastHit hitUp;
  153. RaycastHit hitDown;
  154. Physics.Raycast(bounds.center, Vector3.forward, out hitForward, bounds.extents.z * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
  155. Physics.Raycast(bounds.center, Vector3.back, out hitBack, bounds.extents.z * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
  156. Physics.Raycast(bounds.center, Vector3.left, out hitLeft, bounds.extents.x * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
  157. Physics.Raycast(bounds.center, Vector3.right, out hitRight, bounds.extents.x * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
  158. Physics.Raycast(bounds.center, Vector3.up, out hitUp, bounds.extents.y * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
  159. Physics.Raycast(bounds.center, Vector3.down, out hitDown, bounds.extents.y * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
  160. // shortest valid ray wins
  161. float lengthX = (hitRight.collider != null) ? hitRight.distance : float.MaxValue;
  162. float lengthY = (hitDown.collider != null) ? hitDown.distance : float.MaxValue;
  163. float lengthZ = (hitBack.collider != null) ? hitBack.distance : float.MaxValue;
  164. float lengthNegX = (hitLeft.collider != null) ? hitLeft.distance : float.MaxValue;
  165. float lengthNegY = (hitUp.collider != null) ? hitUp.distance : float.MaxValue;
  166. float lengthNegZ = (hitForward.collider != null) ? hitForward.distance : float.MaxValue;
  167. // TODO: not yet the right decision strategy, works only partially
  168. if (VRTK_SharedMethods.IsLowest(lengthX, new float[] { lengthY, lengthZ, lengthNegX, lengthNegY, lengthNegZ }))
  169. {
  170. returnDirection = KnobDirection.z;
  171. }
  172. else if (VRTK_SharedMethods.IsLowest(lengthY, new float[] { lengthX, lengthZ, lengthNegX, lengthNegY, lengthNegZ }))
  173. {
  174. returnDirection = KnobDirection.y;
  175. }
  176. else if (VRTK_SharedMethods.IsLowest(lengthZ, new float[] { lengthX, lengthY, lengthNegX, lengthNegY, lengthNegZ }))
  177. {
  178. returnDirection = KnobDirection.x;
  179. }
  180. else if (VRTK_SharedMethods.IsLowest(lengthNegX, new float[] { lengthX, lengthY, lengthZ, lengthNegY, lengthNegZ }))
  181. {
  182. returnDirection = KnobDirection.z;
  183. }
  184. else if (VRTK_SharedMethods.IsLowest(lengthNegY, new float[] { lengthX, lengthY, lengthZ, lengthNegX, lengthNegZ }))
  185. {
  186. returnDirection = KnobDirection.y;
  187. }
  188. else if (VRTK_SharedMethods.IsLowest(lengthNegZ, new float[] { lengthX, lengthY, lengthZ, lengthNegX, lengthNegY }))
  189. {
  190. returnDirection = KnobDirection.x;
  191. }
  192. return returnDirection;
  193. }
  194. protected virtual float CalculateValue()
  195. {
  196. if (!subDirectionFound)
  197. {
  198. float angleX = Mathf.Abs(transform.localRotation.eulerAngles.x - initialLocalRotation.x) % 90;
  199. float angleY = Mathf.Abs(transform.localRotation.eulerAngles.y - initialLocalRotation.y) % 90;
  200. float angleZ = Mathf.Abs(transform.localRotation.eulerAngles.z - initialLocalRotation.z) % 90;
  201. angleX = (Mathf.RoundToInt(angleX) >= 89) ? 0 : angleX;
  202. angleY = (Mathf.RoundToInt(angleY) >= 89) ? 0 : angleY;
  203. angleZ = (Mathf.RoundToInt(angleZ) >= 89) ? 0 : angleZ;
  204. if (Mathf.RoundToInt(angleX) != 0 || Mathf.RoundToInt(angleY) != 0 || Mathf.RoundToInt(angleZ) != 0)
  205. {
  206. subDirection = angleX < angleY ? (angleY < angleZ ? KnobDirection.z : KnobDirection.y) : (angleX < angleZ ? KnobDirection.z : KnobDirection.x);
  207. subDirectionFound = true;
  208. }
  209. }
  210. float angle = 0;
  211. switch (subDirection)
  212. {
  213. case KnobDirection.x:
  214. angle = transform.localRotation.eulerAngles.x - initialLocalRotation.x;
  215. break;
  216. case KnobDirection.y:
  217. angle = transform.localRotation.eulerAngles.y - initialLocalRotation.y;
  218. break;
  219. case KnobDirection.z:
  220. angle = transform.localRotation.eulerAngles.z - initialLocalRotation.z;
  221. break;
  222. }
  223. angle = Mathf.Round(angle * 1000f) / 1000f; // not rounding will produce slight offsets in 4th digit that mess up initial value
  224. // Quaternion.angle will calculate shortest route and only go to 180
  225. float calculatedValue = 0;
  226. if (angle > 0 && angle <= 180)
  227. {
  228. calculatedValue = 360 - Quaternion.Angle(initialRotation, transform.rotation);
  229. }
  230. else
  231. {
  232. calculatedValue = Quaternion.Angle(initialRotation, transform.rotation);
  233. }
  234. // adjust to value scale
  235. calculatedValue = Mathf.Round((min + Mathf.Clamp01(calculatedValue / 360f) * (max - min)) / stepSize) * stepSize;
  236. if (min > max && angle != 0)
  237. {
  238. calculatedValue = (max + min) - calculatedValue;
  239. }
  240. return calculatedValue;
  241. }
  242. }
  243. }