ArmModel.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. // Copyright 2016 Google Inc. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Modified by Unity from original:
  15. // https://github.com/googlevr/gvr-unity-sdk/blob/master/Assets/GoogleVR/Scripts/Controller/ArmModel/GvrArmModel.cs
  16. using System.Collections;
  17. using System.Collections.Generic;
  18. using UnityEngine;
  19. #if ENABLE_VR || ENABLE_AR
  20. using UnityEngine.SpatialTracking;
  21. using UnityEngine.Experimental.XR.Interaction;
  22. namespace UnityEngine.XR.LegacyInputHelpers
  23. {
  24. public class ArmModel : BasePoseProvider
  25. {
  26. /// <summary> Gets the Pose value from the calculated arm model. as the model returns both position and rotation in all cases, we set both flags on return if successful.</summary>
  27. public override PoseDataFlags GetPoseFromProvider(out Pose output)
  28. {
  29. if (OnControllerInputUpdated())
  30. {
  31. output = finalPose;
  32. return PoseDataFlags.Position | PoseDataFlags.Rotation;
  33. }
  34. output = Pose.identity;
  35. return PoseDataFlags.NoData;
  36. }
  37. Pose m_FinalPose;
  38. /// <summary>
  39. /// the pose which represents the final tracking result of the arm model
  40. /// </summary>
  41. public Pose finalPose
  42. {
  43. get { return m_FinalPose; }
  44. set { m_FinalPose = value; }
  45. }
  46. [SerializeField]
  47. XRNode m_PoseSource = XRNode.LeftHand;
  48. /// <summary>
  49. /// the pose to use as the input 3DOF position
  50. /// </summary>
  51. public XRNode poseSource
  52. {
  53. get { return m_PoseSource; }
  54. set { m_PoseSource = value; }
  55. }
  56. [SerializeField]
  57. XRNode m_HeadPoseSource = XRNode.CenterEye;
  58. /// <summary>
  59. /// The game object which represents the "head" position of the user
  60. /// </summary>
  61. public XRNode headGameObject
  62. {
  63. get { return m_HeadPoseSource; }
  64. set { m_HeadPoseSource = value; }
  65. }
  66. /// Standard implementation for a mathematical model to make the virtual controller approximate the
  67. /// physical location of the Daydream controller.
  68. [SerializeField]
  69. Vector3 m_ElbowRestPosition = DEFAULT_ELBOW_REST_POSITION;
  70. /// <summary>
  71. /// Position of the elbow joint relative to the head before the arm model is applied.
  72. /// </summary>
  73. public Vector3 elbowRestPosition
  74. {
  75. get { return m_ElbowRestPosition; }
  76. set { m_ElbowRestPosition = value; }
  77. }
  78. [SerializeField]
  79. Vector3 m_WristRestPosition = DEFAULT_WRIST_REST_POSITION;
  80. /// <summary>
  81. /// Position of the wrist joint relative to the elbow before the arm model is applied.
  82. /// </summary>
  83. public Vector3 wristRestPosition
  84. {
  85. get { return m_WristRestPosition; }
  86. set { m_WristRestPosition = value; }
  87. }
  88. [SerializeField]
  89. Vector3 m_ControllerRestPosition = DEFAULT_CONTROLLER_REST_POSITION;
  90. /// <summary>
  91. /// Position of the controller joint relative to the wrist before the arm model is applied.
  92. /// </summary>
  93. public Vector3 controllerRestPosition
  94. {
  95. get { return m_ControllerRestPosition; }
  96. set { m_ControllerRestPosition = value; }
  97. }
  98. [SerializeField]
  99. Vector3 m_ArmExtensionOffset = DEFAULT_ARM_EXTENSION_OFFSET;
  100. /// <summary>
  101. /// Offset applied to the elbow position as the controller is rotated upwards.
  102. /// </summary>
  103. public Vector3 armExtensionOffset
  104. {
  105. get { return m_ArmExtensionOffset; }
  106. set { m_ArmExtensionOffset = value; }
  107. }
  108. [Range(0.0f, 1.0f)]
  109. [SerializeField]
  110. float m_ElbowBendRatio = DEFAULT_ELBOW_BEND_RATIO;
  111. /// <summary>
  112. /// Ratio of the controller's rotation to apply to the rotation of the elbow.
  113. /// The remaining rotation is applied to the wrist's rotation.
  114. /// </summary>
  115. public float elbowBendRatio
  116. {
  117. get { return m_ElbowBendRatio; }
  118. set { m_ElbowBendRatio = value; }
  119. }
  120. [SerializeField]
  121. bool m_IsLockedToNeck = true;
  122. /// <summary>
  123. /// If true, the root of the pose is locked to the local position of the player's neck.
  124. /// </summary>
  125. public bool isLockedToNeck
  126. {
  127. get { return m_IsLockedToNeck; }
  128. set { m_IsLockedToNeck = value; }
  129. }
  130. /// Represent the neck's position relative to the user's head.
  131. /// If isLockedToNeck is true, this will be the InputTracking position of the Head node modified
  132. /// by an inverse neck model to approximate the neck position.
  133. /// Otherwise, it is always zero.
  134. public Vector3 neckPosition
  135. {
  136. get
  137. {
  138. return m_NeckPosition;
  139. }
  140. }
  141. /// Represent the shoulder's position relative to the user's head.
  142. /// This is not actually used as part of the arm model calculations, and exists for debugging.
  143. public Vector3 shoulderPosition
  144. {
  145. get
  146. {
  147. Vector3 retVal = m_NeckPosition + m_TorsoRotation * Vector3.Scale(SHOULDER_POSITION, m_HandedMultiplier);
  148. return retVal;
  149. }
  150. }
  151. /// Represent the shoulder's rotation relative to the user's head.
  152. /// This is not actually used as part of the arm model calculations, and exists for debugging.
  153. public Quaternion shoulderRotation
  154. {
  155. get
  156. {
  157. return m_TorsoRotation;
  158. }
  159. }
  160. /// Represent the elbow's position relative to the user's head.
  161. public Vector3 elbowPosition
  162. {
  163. get
  164. {
  165. return m_ElbowPosition;
  166. }
  167. }
  168. /// Represent the elbow's rotation relative to the user's head.
  169. public Quaternion elbowRotation
  170. {
  171. get
  172. {
  173. return m_ElbowRotation;
  174. }
  175. }
  176. /// Represent the wrist's position relative to the user's head.
  177. public Vector3 wristPosition
  178. {
  179. get
  180. {
  181. return m_WristPosition;
  182. }
  183. }
  184. /// Represent the wrist's rotation relative to the user's head.
  185. public Quaternion wristRotation
  186. {
  187. get
  188. {
  189. return m_WristRotation;
  190. }
  191. }
  192. /// Represent the controller's position relative to the head pose
  193. public Vector3 controllerPosition
  194. {
  195. get
  196. {
  197. return m_ControllerPosition;
  198. }
  199. }
  200. /// Represent the controllers rotation relative to the user's head.
  201. public Quaternion controllerRotation
  202. {
  203. get
  204. {
  205. return m_ControllerRotation;
  206. }
  207. }
  208. #if UNITY_EDITOR
  209. /// Editor only API to allow querying the torso forward direction
  210. public Vector3 torsoDirection
  211. {
  212. get { return m_TorsoDirection; }
  213. }
  214. /// Editor only API to allow querying the torso rotation
  215. public Quaternion torsoRotation
  216. {
  217. get { return m_TorsoRotation; }
  218. }
  219. #endif
  220. protected Vector3 m_NeckPosition;
  221. protected Vector3 m_ElbowPosition;
  222. protected Quaternion m_ElbowRotation;
  223. protected Vector3 m_WristPosition;
  224. protected Quaternion m_WristRotation;
  225. protected Vector3 m_ControllerPosition;
  226. protected Quaternion m_ControllerRotation;
  227. /// Multiplier for handedness such that 1 = Right, 0 = Center, -1 = left.
  228. protected Vector3 m_HandedMultiplier;
  229. /// Forward direction of user's torso.
  230. protected Vector3 m_TorsoDirection;
  231. /// Orientation of the user's torso.
  232. protected Quaternion m_TorsoRotation;
  233. // Default values for tuning variables.
  234. protected static readonly Vector3 DEFAULT_ELBOW_REST_POSITION = new Vector3(0.195f, -0.5f, 0.005f);
  235. protected static readonly Vector3 DEFAULT_WRIST_REST_POSITION = new Vector3(0.0f, 0.0f, 0.25f);
  236. protected static readonly Vector3 DEFAULT_CONTROLLER_REST_POSITION = new Vector3(0.0f, 0.0f, 0.05f);
  237. protected static readonly Vector3 DEFAULT_ARM_EXTENSION_OFFSET = new Vector3(-0.13f, 0.14f, 0.08f);
  238. protected const float DEFAULT_ELBOW_BEND_RATIO = 0.6f;
  239. /// Increases elbow bending as the controller moves up (unitless).
  240. protected const float EXTENSION_WEIGHT = 0.4f;
  241. /// Rest position for shoulder joint.
  242. protected static readonly Vector3 SHOULDER_POSITION = new Vector3(0.17f, -0.2f, -0.03f);
  243. /// Neck offset used to apply the inverse neck model when locked to the head.
  244. protected static readonly Vector3 NECK_OFFSET = new Vector3(0.0f, 0.075f, 0.08f);
  245. /// Angle ranges the for arm extension offset to start and end (degrees).
  246. protected const float MIN_EXTENSION_ANGLE = 7.0f;
  247. protected const float MAX_EXTENSION_ANGLE = 60.0f;
  248. protected virtual void OnEnable()
  249. {
  250. // Force the torso direction to match the gaze direction immediately.
  251. // Otherwise, the controller will not be positioned correctly if the ArmModel was enabled
  252. // when the user wasn't facing forward.
  253. UpdateTorsoDirection(true);
  254. // Update immediately to avoid a frame delay before the arm model is applied.
  255. OnControllerInputUpdated();
  256. }
  257. protected virtual void OnDisable()
  258. {
  259. }
  260. public virtual bool OnControllerInputUpdated()
  261. {
  262. UpdateHandedness();
  263. if (UpdateTorsoDirection(false))
  264. {
  265. if (UpdateNeckPosition())
  266. {
  267. if (ApplyArmModel())
  268. {
  269. return true;
  270. }
  271. }
  272. }
  273. return false;
  274. }
  275. protected virtual void UpdateHandedness()
  276. {
  277. // Determine handedness multiplier.
  278. m_HandedMultiplier.Set(0, 1, 1);
  279. if (m_PoseSource == XRNode.RightHand || m_PoseSource == XRNode.TrackingReference)
  280. {
  281. m_HandedMultiplier.x = 1.0f;
  282. }
  283. else if (m_PoseSource == XRNode.LeftHand)
  284. {
  285. m_HandedMultiplier.x = -1.0f;
  286. }
  287. }
  288. protected virtual bool UpdateTorsoDirection(bool forceImmediate)
  289. {
  290. // Determine the gaze direction horizontally.
  291. Vector3 gazeDirection = new Vector3();
  292. if (TryGetForwardVector(m_HeadPoseSource, out gazeDirection))
  293. {
  294. gazeDirection.y = 0.0f;
  295. gazeDirection.Normalize();
  296. // Use the gaze direction to update the forward direction.
  297. if (forceImmediate)
  298. {
  299. m_TorsoDirection = gazeDirection;
  300. }
  301. else
  302. {
  303. Vector3 angAccel;
  304. if (TryGetAngularAcceleration(poseSource, out angAccel))
  305. {
  306. float angularVelocity = angAccel.magnitude;
  307. float gazeFilterStrength = Mathf.Clamp((angularVelocity - 0.2f) / 45.0f, 0.0f, 0.1f);
  308. m_TorsoDirection = Vector3.Slerp(m_TorsoDirection, gazeDirection, gazeFilterStrength);
  309. }
  310. }
  311. // Calculate the torso rotation.
  312. m_TorsoRotation = Quaternion.FromToRotation(Vector3.forward, m_TorsoDirection);
  313. return true;
  314. }
  315. return false;
  316. }
  317. protected virtual bool UpdateNeckPosition()
  318. {
  319. if (m_IsLockedToNeck && TryGetPosition(m_HeadPoseSource, out m_NeckPosition))
  320. {
  321. // Find the approximate neck position by Applying an inverse neck model.
  322. // This transforms the head position to the center of the head and also accounts
  323. // for the head's rotation so that the motion feels more natural.
  324. return ApplyInverseNeckModel(m_NeckPosition, out m_NeckPosition);
  325. }
  326. else
  327. {
  328. m_NeckPosition = Vector3.zero;
  329. return true;
  330. }
  331. }
  332. protected virtual bool ApplyArmModel()
  333. {
  334. // Set the starting positions of the joints before they are transformed by the arm model.
  335. SetUntransformedJointPositions();
  336. // Get the controller's orientation.
  337. Quaternion controllerOrientation;
  338. Quaternion xyRotation;
  339. float xAngle;
  340. if (GetControllerRotation(out controllerOrientation, out xyRotation, out xAngle))
  341. {
  342. // Offset the elbow by the extension offset.
  343. float extensionRatio = CalculateExtensionRatio(xAngle);
  344. ApplyExtensionOffset(extensionRatio);
  345. // Calculate the lerp rotation, which is used to control how much the rotation of the
  346. // controller impacts each joint.
  347. Quaternion lerpRotation = CalculateLerpRotation(xyRotation, extensionRatio);
  348. CalculateFinalJointRotations(controllerOrientation, xyRotation, lerpRotation);
  349. ApplyRotationToJoints();
  350. m_FinalPose.position = m_ControllerPosition;
  351. m_FinalPose.rotation = m_ControllerRotation;
  352. return true;
  353. }
  354. return false;
  355. }
  356. /// Set the starting positions of the joints before they are transformed by the arm model.
  357. protected virtual void SetUntransformedJointPositions()
  358. {
  359. m_ElbowPosition = Vector3.Scale(m_ElbowRestPosition, m_HandedMultiplier);
  360. m_WristPosition = Vector3.Scale(m_WristRestPosition, m_HandedMultiplier);
  361. m_ControllerPosition = Vector3.Scale(m_ControllerRestPosition, m_HandedMultiplier);
  362. }
  363. /// Calculate the extension ratio based on the angle of the controller along the x axis.
  364. protected virtual float CalculateExtensionRatio(float xAngle)
  365. {
  366. float normalizedAngle = (xAngle - MIN_EXTENSION_ANGLE) / (MAX_EXTENSION_ANGLE - MIN_EXTENSION_ANGLE);
  367. float extensionRatio = Mathf.Clamp(normalizedAngle, 0.0f, 1.0f);
  368. return extensionRatio;
  369. }
  370. /// Offset the elbow by the extension offset.
  371. protected virtual void ApplyExtensionOffset(float extensionRatio)
  372. {
  373. Vector3 extensionOffset = Vector3.Scale(m_ArmExtensionOffset, m_HandedMultiplier);
  374. m_ElbowPosition += extensionOffset * extensionRatio;
  375. }
  376. /// Calculate the lerp rotation, which is used to control how much the rotation of the
  377. /// controller impacts each joint.
  378. protected virtual Quaternion CalculateLerpRotation(Quaternion xyRotation, float extensionRatio)
  379. {
  380. float totalAngle = Quaternion.Angle(xyRotation, Quaternion.identity);
  381. float lerpSuppresion = 1.0f - Mathf.Pow(totalAngle / 180.0f, 6.0f);
  382. float inverseElbowBendRatio = 1.0f - m_ElbowBendRatio;
  383. float lerpValue = inverseElbowBendRatio + m_ElbowBendRatio * extensionRatio * EXTENSION_WEIGHT;
  384. lerpValue *= lerpSuppresion;
  385. return Quaternion.Lerp(Quaternion.identity, xyRotation, lerpValue);
  386. }
  387. /// Determine the final joint rotations relative to the head.
  388. protected virtual void CalculateFinalJointRotations(Quaternion controllerOrientation, Quaternion xyRotation, Quaternion lerpRotation)
  389. {
  390. m_ElbowRotation = m_TorsoRotation * Quaternion.Inverse(lerpRotation) * xyRotation;
  391. m_WristRotation = m_ElbowRotation * lerpRotation;
  392. m_ControllerRotation = m_TorsoRotation * controllerOrientation;
  393. }
  394. /// Apply the joint rotations to the positions of the joints to determine the final pose.
  395. protected virtual void ApplyRotationToJoints()
  396. {
  397. m_ElbowPosition = m_NeckPosition + m_TorsoRotation * m_ElbowPosition;
  398. m_WristPosition = m_ElbowPosition + m_ElbowRotation * m_WristPosition;
  399. m_ControllerPosition = m_WristPosition + m_WristRotation * m_ControllerPosition;
  400. }
  401. /// Transform the head position into an approximate neck position.
  402. protected virtual bool ApplyInverseNeckModel(Vector3 headPosition, out Vector3 calculatedPosition)
  403. {
  404. // Determine the gaze direction horizontally.
  405. Quaternion headRotation = new Quaternion();
  406. if (TryGetRotation(m_HeadPoseSource, out headRotation))
  407. {
  408. Vector3 rotatedNeckOffset =
  409. headRotation * NECK_OFFSET - NECK_OFFSET.y * Vector3.up;
  410. headPosition -= rotatedNeckOffset;
  411. calculatedPosition = headPosition;
  412. return true;
  413. }
  414. calculatedPosition = Vector3.zero;
  415. return false;
  416. }
  417. protected bool TryGetForwardVector(XRNode node, out Vector3 forward)
  418. {
  419. Pose tmpPose = new Pose();
  420. if (TryGetRotation(node, out tmpPose.rotation) &&
  421. TryGetPosition(node, out tmpPose.position))
  422. {
  423. forward = tmpPose.forward;
  424. return true;
  425. }
  426. forward = Vector3.zero;
  427. return false;
  428. }
  429. List<XR.XRNodeState> xrNodeStateListOrientation = new List<XRNodeState>();
  430. protected bool TryGetRotation(XRNode node, out Quaternion rotation)
  431. {
  432. XR.InputTracking.GetNodeStates(xrNodeStateListOrientation);
  433. var length = xrNodeStateListOrientation.Count;
  434. XRNodeState nodeState;
  435. for (int i = 0; i < length; ++i)
  436. {
  437. nodeState = xrNodeStateListOrientation[i];
  438. if (nodeState.nodeType == node)
  439. {
  440. if (nodeState.TryGetRotation(out rotation))
  441. {
  442. return true;
  443. }
  444. }
  445. }
  446. rotation = Quaternion.identity;
  447. return false;
  448. }
  449. List<XR.XRNodeState> xrNodeStateListPosition = new List<XRNodeState>();
  450. protected bool TryGetPosition(XRNode node, out Vector3 position)
  451. {
  452. XR.InputTracking.GetNodeStates(xrNodeStateListPosition);
  453. var length = xrNodeStateListPosition.Count;
  454. XRNodeState nodeState;
  455. for (int i = 0; i < length; ++i)
  456. {
  457. nodeState = xrNodeStateListPosition[i];
  458. if (nodeState.nodeType == node)
  459. {
  460. if (nodeState.TryGetPosition(out position))
  461. {
  462. return true;
  463. }
  464. }
  465. }
  466. position = Vector3.zero;
  467. return false;
  468. }
  469. List<XR.XRNodeState> xrNodeStateListAngularAcceleration = new List<XRNodeState>();
  470. protected bool TryGetAngularAcceleration(XRNode node, out Vector3 angularAccel)
  471. {
  472. XR.InputTracking.GetNodeStates(xrNodeStateListAngularAcceleration);
  473. var length = xrNodeStateListAngularAcceleration.Count;
  474. XRNodeState nodeState;
  475. for (int i = 0; i < length; ++i)
  476. {
  477. nodeState = xrNodeStateListAngularAcceleration[i];
  478. if (nodeState.nodeType == node)
  479. {
  480. if (nodeState.TryGetAngularAcceleration(out angularAccel))
  481. {
  482. return true;
  483. }
  484. }
  485. }
  486. angularAccel = Vector3.zero;
  487. return false;
  488. }
  489. List<XR.XRNodeState> xrNodeStateListAngularVelocity = new List<XRNodeState>();
  490. protected bool TryGetAngularVelocity(XRNode node, out Vector3 angVel)
  491. {
  492. XR.InputTracking.GetNodeStates(xrNodeStateListAngularVelocity);
  493. var length = xrNodeStateListAngularVelocity.Count;
  494. XRNodeState nodeState;
  495. for (int i = 0; i < length; ++i)
  496. {
  497. nodeState = xrNodeStateListAngularVelocity[i];
  498. if (nodeState.nodeType == node)
  499. {
  500. if (nodeState.TryGetAngularVelocity(out angVel))
  501. {
  502. return true;
  503. }
  504. }
  505. }
  506. angVel = Vector3.zero;
  507. return false;
  508. }
  509. /// Get the controller's orientation.
  510. protected bool GetControllerRotation(out Quaternion rotation, out Quaternion xyRotation, out float xAngle)
  511. {
  512. // Find the controller's orientation relative to the player.
  513. if (TryGetRotation(poseSource, out rotation))
  514. {
  515. rotation = Quaternion.Inverse(m_TorsoRotation) * rotation;
  516. // Extract just the x rotation angle.
  517. Vector3 controllerForward = rotation * Vector3.forward;
  518. xAngle = 90.0f - Vector3.Angle(controllerForward, Vector3.up);
  519. // Remove the z rotation from the controller.
  520. xyRotation = Quaternion.FromToRotation(Vector3.forward, controllerForward);
  521. return true;
  522. }
  523. else
  524. {
  525. rotation = Quaternion.identity;
  526. xyRotation = Quaternion.identity;
  527. xAngle = 0.0f;
  528. return false;
  529. }
  530. }
  531. #if UNITY_EDITOR
  532. /// <summary>
  533. /// Editor only API to draw debug gizmos to help visualize the arm model
  534. /// </summary>
  535. public virtual void OnDrawGizmos()
  536. {
  537. if (!enabled)
  538. {
  539. return;
  540. }
  541. if (transform.parent == null) {
  542. return;
  543. }
  544. Vector3 worldShoulder = transform.parent.TransformPoint(shoulderPosition);
  545. Vector3 worldElbow = transform.parent.TransformPoint(elbowPosition);
  546. Vector3 worldwrist = transform.parent.TransformPoint(wristPosition);
  547. Vector3 worldcontroller = transform.parent.TransformPoint(controllerPosition);
  548. Gizmos.color = Color.red;
  549. Gizmos.DrawSphere(worldShoulder, 0.02f);
  550. Gizmos.DrawLine(worldShoulder, worldElbow);
  551. Gizmos.color = Color.green;
  552. Gizmos.DrawSphere(worldElbow, 0.02f);
  553. Gizmos.DrawLine(worldElbow, worldwrist);
  554. Gizmos.color = Color.cyan;
  555. Gizmos.DrawSphere(worldwrist, 0.02f);
  556. Gizmos.color = Color.blue;
  557. Gizmos.DrawSphere(worldcontroller, 0.02f);
  558. }
  559. #endif // UNITY_EDITOR
  560. }
  561. }
  562. #endif