static IEnumerator WaitForSteadyState(XRBaseInteractable.MovementType movementType)
        {
            yield return(null);

            if (movementType == XRBaseInteractable.MovementType.VelocityTracking)
            {
                yield return(new WaitForFixedUpdate());
            }

            yield return(new WaitForFixedUpdate());
        }
        /// <summary>This method is called by the interaction manager
        /// when the interactor ends selection of an interactable.</summary>
        /// <param name="interactor">Interactor that is ending the selection.</param>
        protected internal override void OnSelectExit(XRBaseInteractor interactor)
        {
            base.OnSelectExit(interactor);

            // reset RididBody settings
            m_RigidBody.isKinematic = m_WasKinematic;
            m_RigidBody.useGravity  = m_UsedGravity | m_GravityOnDetach;

            m_CurrentMovementType = m_MovementType;
            m_SelectingInteractor = null;
            m_DetachInLateUpdate  = true;
            SmoothVelocityEnd();
        }
        protected override void Awake()
        {
            base.Awake();

            m_CurrentMovementType = m_MovementType;
            if (m_RigidBody == null)
            {
                m_RigidBody = GetComponent <Rigidbody>();
            }
            if (m_RigidBody == null)
            {
                Debug.LogWarning("Grab Interactable does not have a required RigidBody.", this);
            }
        }
        /// <summary>This method is called by the interaction manager
        /// when the interactor first initiates selection of an interactable.</summary>
        /// <param name="interactor">Interactor that is initiating the selection.</param>
        protected internal override void OnSelectEnter(XRBaseInteractor interactor)
        {
            if (!interactor)
            {
                return;
            }
            base.OnSelectEnter(interactor);

            // Only do this on the first select, because otherwise the original parent will be overwritten as null.
            if (m_SelectingInteractor == null)
            {
                m_OriginalSceneParent = transform.parent;
                transform.parent      = null;
            }

            m_SelectingInteractor = interactor;

            // special case where the interactor will override this objects movement type (used for Sockets and other absolute interactors)
            m_CurrentMovementType = (interactor.selectedInteractableMovementTypeOverride != null) ? interactor.selectedInteractableMovementTypeOverride.Value : m_MovementType;

            // remember RigidBody settings and setup to move
            m_WasKinematic          = m_RigidBody.isKinematic;
            m_UsedGravity           = m_RigidBody.useGravity;
            m_RigidBody.isKinematic = (m_CurrentMovementType == XRBaseInteractable.MovementType.Kinematic);
            m_RigidBody.useGravity  = false;
            m_RigidBody.drag        = 0f;
            m_RigidBody.angularDrag = 0f;

            // forget if we have previous detach velocities
            m_DetachVelocity = m_DetachAngularVelocity = Vector3.zero;

            UpdateInteractorLocalPose(interactor);

            var teleportOnSelect = false;

            if (teleportOnSelect)
            {
                Teleport(m_SelectingInteractor.attachTransform);
            }
            else if (m_AttachEaseInTime > 0.0f)
            {
                m_TargetWorldPosition   = m_RigidBody.worldCenterOfMass;
                m_TargetWorldRotation   = transform.rotation;
                m_CurrentAttachEaseTime = 0f;
            }

            SmoothVelocityStart();
        }
        public IEnumerator CenteredObjectWithAttachTransformMovesToExpectedPosition([ValueSource(nameof(s_MovementTypes))] XRBaseInteractable.MovementType movementType)
        {
            // Create Grab Interactable at some arbitrary point
            var grabInteractableGO = GameObject.CreatePrimitive(PrimitiveType.Cube);

            grabInteractableGO.name = "Grab Interactable";
            grabInteractableGO.transform.localPosition = new Vector3(1f, 2f, 3f);
            grabInteractableGO.transform.localRotation = Quaternion.identity;
            var boxCollider = grabInteractableGO.GetComponent <BoxCollider>();
            var rigidbody   = grabInteractableGO.AddComponent <Rigidbody>();

            rigidbody.useGravity  = false;
            rigidbody.isKinematic = true;
            var grabInteractable = grabInteractableGO.AddComponent <XRGrabInteractable>();

            grabInteractable.movementType = movementType;
            DisableDelayProperties(grabInteractable);
            // Set the Attach Transform to the back upper-right corner of the cube
            // to test an attach transform different from the transform position (which is also its center).
            var grabInteractableAttach = new GameObject("Grab Interactable Attach").transform;
            var attachOffset           = new Vector3(0.5f, 0.5f, -0.5f);

            grabInteractableAttach.SetParent(grabInteractable.transform);
            grabInteractableAttach.localPosition = attachOffset;
            grabInteractableAttach.localRotation = Quaternion.identity;
            grabInteractable.attachTransform     = grabInteractableAttach;
            // The built-in Cube resource has its center at the center of the cube.
            var centerOffset = Vector3.zero;

            Assert.That(grabInteractable.attachPointCompatibilityMode, Is.EqualTo(XRGrabInteractable.AttachPointCompatibilityMode.Default));

            // Wait for physics update to ensure the Rigidbody is stable and center of mass has been calculated
            yield return(new WaitForFixedUpdate());

            yield return(null);

            Assert.That(grabInteractable.isSelected, Is.False);
            Assert.That(grabInteractable.attachTransform.position, Is.EqualTo(new Vector3(1f, 2f, 3f) + attachOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.attachTransform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(grabInteractable.transform.position, Is.EqualTo(new Vector3(1f, 2f, 3f)).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.transform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(boxCollider, Is.Not.Null);
            Assert.That(boxCollider.center, Is.EqualTo(centerOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.position, Is.EqualTo(new Vector3(1f, 2f, 3f)).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.centerOfMass, Is.EqualTo(centerOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.worldCenterOfMass, Is.EqualTo(new Vector3(1f, 2f, 3f) + centerOffset).Using(Vector3ComparerWithEqualsOperator.Instance));

            // Create Interactor at some arbitrary point away from the Interactable
            var interactorGO   = new GameObject("Interactor");
            var targetPosition = Vector3.zero;

            interactorGO.transform.localPosition = Vector3.zero;
            interactorGO.transform.localRotation = Quaternion.identity;
            var interactor = interactorGO.AddComponent <MockInteractor>();

            yield return(null);

            Assert.That(grabInteractable.isSelected, Is.False);
            Assert.That(interactor.selectTarget, Is.Null);
            Assert.That(interactor.attachTransform, Is.Not.Null);
            Assert.That(interactor.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(interactor.attachTransform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));

            // Set valid so it will be selected next frame by the Interaction Manager
            interactor.validTargets.Add(grabInteractable);

            yield return(WaitForSteadyState(movementType));

            Assert.That(grabInteractable.isSelected, Is.True);
            Assert.That(grabInteractable.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.attachTransform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(grabInteractable.transform.position, Is.EqualTo(targetPosition - attachOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.transform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(interactor.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(interactor.attachTransform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(rigidbody.position, Is.EqualTo(targetPosition - attachOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));

            // Move the attach transform on the Interactor after being grabbed
            targetPosition = new Vector3(5f, 5f, 5f);
            interactor.attachTransform.position = targetPosition;

            yield return(WaitForSteadyState(movementType));

            Assert.That(grabInteractable.isSelected, Is.True);
            Assert.That(grabInteractable.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.attachTransform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(grabInteractable.transform.position, Is.EqualTo(targetPosition - attachOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.transform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(interactor.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(interactor.attachTransform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(rigidbody.position, Is.EqualTo(targetPosition - attachOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));

            // Move the attach transform on the Interactable to the back lower-right corner of the cube
            attachOffset = new Vector3(0.5f, -0.5f, -0.5f);
            grabInteractable.attachTransform.localPosition = attachOffset;

            yield return(WaitForSteadyState(movementType));

            Assert.That(grabInteractable.isSelected, Is.True);
            Assert.That(grabInteractable.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.attachTransform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(grabInteractable.transform.position, Is.EqualTo(targetPosition - attachOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.transform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(interactor.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(interactor.attachTransform.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
            Assert.That(rigidbody.position, Is.EqualTo(targetPosition - attachOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.rotation, Is.EqualTo(Quaternion.identity).Using(QuaternionEqualityComparer.Instance));
        }
        public IEnumerator NonCenteredObjectRotatesToExpectedOrientation([ValueSource(nameof(s_MovementTypes))] XRBaseInteractable.MovementType movementType)
        {
            // Create a cube mesh with the pivot position at the bottom center
            // rather than the built-in Cube resource which has its center at the
            // center of the cube.
            var cubePrimitiveGO   = GameObject.CreatePrimitive(PrimitiveType.Cube);
            var cubePrimitiveMesh = cubePrimitiveGO.GetComponent <MeshFilter>().sharedMesh;
            var centerOffset      = new Vector3(0f, 0.5f, 0f);
            var offCenterMesh     = new Mesh
            {
                vertices  = cubePrimitiveMesh.vertices.Select(vertex => vertex + centerOffset).ToArray(),
                triangles = cubePrimitiveMesh.triangles.ToArray(),
                normals   = cubePrimitiveMesh.normals.ToArray(),
            };

            cubePrimitiveGO.SetActive(false);
            Object.Destroy(cubePrimitiveGO);

            // Create Grab Interactable at some arbitrary point
            var grabInteractableGO = new GameObject("Grab Interactable");

            grabInteractableGO.transform.localPosition = new Vector3(1f, 2f, 3f);
            grabInteractableGO.transform.localRotation = Quaternion.LookRotation(Vector3.back, Vector3.up);
            var meshFilter = grabInteractableGO.AddComponent <MeshFilter>();

            meshFilter.sharedMesh = offCenterMesh;
            grabInteractableGO.AddComponent <MeshRenderer>();
            var boxCollider = grabInteractableGO.AddComponent <BoxCollider>();
            var rigidbody   = grabInteractableGO.AddComponent <Rigidbody>();

            rigidbody.useGravity  = false;
            rigidbody.isKinematic = true;
            var grabInteractable = grabInteractableGO.AddComponent <XRGrabInteractable>();

            grabInteractable.movementType = movementType;
            DisableDelayProperties(grabInteractable);
            // Set the Attach Transform to the back upper-right corner of the cube
            // to test an attach transform different from both the transform position and center.
            var grabInteractableAttach = new GameObject("Grab Interactable Attach").transform;
            var attachOffset           = new Vector3(0.5f, 1f, -0.5f);

            grabInteractableAttach.SetParent(grabInteractable.transform);
            grabInteractableAttach.localPosition = attachOffset;
            var attachRotation = Quaternion.LookRotation(Vector3.left, Vector3.forward);

            grabInteractableAttach.rotation  = attachRotation;
            grabInteractable.attachTransform = grabInteractableAttach;

            Assert.That(grabInteractable.attachPointCompatibilityMode, Is.EqualTo(XRGrabInteractable.AttachPointCompatibilityMode.Default));

            // Wait for physics update to ensure the Rigidbody is stable and center of mass has been calculated
            yield return(new WaitForFixedUpdate());

            yield return(null);

            Assert.That(grabInteractable.isSelected, Is.False);
            // The Grab Interactable is rotated 180 degrees around the y-axis,
            // so the Attach Transform becomes the front upper-left corner of the cube from the perspective of the world axes,
            // so the position will end up at (0.5, 3, 3.5).
            var worldAttachOffset = new Vector3(-0.5f, 1f, 0.5f);

            Assert.That(grabInteractable.attachTransform.position, Is.EqualTo(new Vector3(1f, 2f, 3f) + worldAttachOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.transform.position, Is.EqualTo(new Vector3(1f, 2f, 3f)).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(boxCollider.center, Is.EqualTo(centerOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.position, Is.EqualTo(new Vector3(1f, 2f, 3f)).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.centerOfMass, Is.EqualTo(centerOffset).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.worldCenterOfMass, Is.EqualTo(new Vector3(1f, 2f, 3f) + centerOffset).Using(Vector3ComparerWithEqualsOperator.Instance));

            // Create Interactor at some arbitrary point away from the Interactable
            var interactorGO   = new GameObject("Interactor");
            var targetPosition = Vector3.zero;
            var targetRotation = Quaternion.identity;

            interactorGO.transform.localPosition = Vector3.zero;
            interactorGO.transform.localRotation = Quaternion.identity;
            var interactor = interactorGO.AddComponent <MockInteractor>();

            yield return(null);

            Assert.That(grabInteractable.isSelected, Is.False);
            Assert.That(interactor.selectTarget, Is.Null);
            Assert.That(interactor.attachTransform, Is.Not.Null);
            Assert.That(interactor.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(interactor.attachTransform.rotation, Is.EqualTo(targetRotation).Using(QuaternionEqualityComparer.Instance));

            // Set valid so it will be selected next frame by the Interaction Manager
            interactor.validTargets.Add(grabInteractable);

            yield return(WaitForSteadyState(movementType));

            Assert.That(grabInteractable.isSelected, Is.True);
            // When the Grab Interactable moves to align with the Interactor's Attach Transform at the origin,
            // the cube should end up with the transform pivot on the right face from the perspective of the world axes
            // to have the Attach Transform there pointing forward.
            var expectedRotation = Quaternion.LookRotation(Vector3.down, Vector3.left);

            Assert.That(grabInteractable.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.attachTransform.rotation, Is.EqualTo(targetRotation).Using(QuaternionEqualityComparer.Instance));
            Assert.That(grabInteractable.transform.position, Is.EqualTo(new Vector3(1f, -0.5f, -0.5f)).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(grabInteractable.transform.rotation, Is.EqualTo(expectedRotation).Using(QuaternionEqualityComparer.Instance));
            Assert.That(interactor.attachTransform.position, Is.EqualTo(targetPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(interactor.attachTransform.rotation, Is.EqualTo(targetRotation).Using(QuaternionEqualityComparer.Instance));
            Assert.That(rigidbody.position, Is.EqualTo(new Vector3(1f, -0.5f, -0.5f)).Using(Vector3ComparerWithEqualsOperator.Instance));
            Assert.That(rigidbody.rotation, Is.EqualTo(expectedRotation).Using(QuaternionEqualityComparer.Instance));
        }