private void UpdatePointer(PointerData pointer)
        {
            // Call the pointer's OnPreSceneQuery function
            // This will give it a chance to prepare itself for raycasts
            // e.g., by building its Rays array
            pointer.Pointer.OnPreSceneQuery();

            // If pointer interaction isn't enabled, clear its result object and return
            if (!pointer.Pointer.IsInteractionEnabled)
            {
                // Don't clear the previous focused object since we still want to trigger FocusExit events
                pointer.ResetFocusedObjects(false);
            }
            else
            {
                // If the pointer is locked, keep the focused object the same.
                // This will ensure that we execute events on those objects
                // even if the pointer isn't pointing at them.
                if (pointer.Pointer.IsFocusLocked && pointer.Pointer.IsTargetPositionLockedOnFocusLock)
                {
                    pointer.UpdateFocusLockedHit();
                }
                else
                {
                    LayerMask[] prioritizedLayerMasks = (pointer.Pointer.PrioritizedLayerMasksOverride ?? FocusLayerMasks);

                    // Perform raycast to determine focused object
                    hitResult3d.Clear();
                    QueryScene(pointer.Pointer, prioritizedLayerMasks, hitResult3d);
                    PointerHitResult hit = hitResult3d;

                    // If we have a unity event system, perform graphics raycasts as well to support Unity UI interactions
                    if (EventSystem.current != null)
                    {
                        // NOTE: We need to do this AFTER RaycastPhysics so we use the current hit point to perform the correct 2D UI Raycast.
                        hitResultUi.Clear();
                        RaycastGraphics(pointer.Pointer, pointer.GraphicEventData, prioritizedLayerMasks, hitResultUi);

                        hit = GetPrioritizedHitResult(hit, hitResultUi, prioritizedLayerMasks);
                    }

                    // Make sure to keep focus on the previous object if focus is locked (no target position lock here).
                    if (pointer.Pointer.IsFocusLocked && pointer.Pointer.Result?.CurrentPointerTarget != null)
                    {
                        hit.hitObject = pointer.Pointer.Result.CurrentPointerTarget;
                    }

                    // Apply the hit result only now so changes in the current target are detected only once per frame.
                    pointer.UpdateHit(hit);

                    // Set the pointer's result last
                    pointer.Pointer.Result = pointer;
                }
            }

            // Call the pointer's OnPostSceneQuery function.
            // This will give it a chance to respond to raycast results
            // e.g., by updating its appearance.
            pointer.Pointer.OnPostSceneQuery();
        }
예제 #2
0
            public void UpdateHit(PointerHitResult hitResult)
            {
                if (hitResult.HitObject != CurrentPointerTarget)
                {
                    // Pointer.OnPreCurrentPointerTargetChange();

                    // Set to default:
                    Pointer.IsTargetPositionLockedOnFocusLock = true;
                }

                PreviousPointerTarget = CurrentPointerTarget;

                focusDetails.Object                    = hitResult.HitObject;
                focusDetails.LastRaycastHit            = hitResult.RaycastHit;
                focusDetails.LastGraphicsRaycastResult = hitResult.GraphicsRaycastResult;

                if (hitResult.RayStepIndex >= 0)
                {
                    RayStepIndex = hitResult.RayStepIndex;
                    StartPoint   = hitResult.Ray.Origin;

                    focusDetails.RayDistance = hitResult.RayDistance;
                    focusDetails.Point       = hitResult.HitPointOnObject;
                    focusDetails.Normal      = hitResult.HitNormalOnObject;
                }
                else
                {
                    // If we don't have a valid ray cast, use the whole pointer ray.
                    var firstStep = Pointer.Rays[0];
                    var finalStep = Pointer.Rays[Pointer.Rays.Length - 1];
                    RayStepIndex = 0;

                    StartPoint = firstStep.Origin;

                    var rayDistance = 0.0f;

                    for (int i = 0; i < Pointer.Rays.Length; i++)
                    {
                        rayDistance += Pointer.Rays[i].Length;
                    }

                    focusDetails.RayDistance = rayDistance;
                    focusDetails.Point       = finalStep.Terminus;
                    focusDetails.Normal      = -finalStep.Direction;
                }

                if (hitResult.HitObject != null)
                {
                    focusDetails.PointLocalSpace  = hitResult.HitObject.transform.InverseTransformPoint(focusDetails.Point);
                    focusDetails.NormalLocalSpace = hitResult.HitObject.transform.InverseTransformDirection(focusDetails.Normal);
                }
                else
                {
                    focusDetails.PointLocalSpace  = Vector3.zero;
                    focusDetails.NormalLocalSpace = Vector3.zero;
                }
            }
예제 #3
0
        private PointerHitResult GetPrioritizedHitResult(PointerHitResult hit1, PointerHitResult hit2, LayerMask[] prioritizedLayerMasks)
        {
            if (hit1.HitObject != null && hit2.HitObject != null)
            {
                // Check layer prioritization.
                if (prioritizedLayerMasks.Length > 1)
                {
                    // Get the index in the prioritized layer masks
                    int layerIndex1 = hit1.HitObject.layer.FindLayerListIndex(prioritizedLayerMasks);
                    int layerIndex2 = hit2.HitObject.layer.FindLayerListIndex(prioritizedLayerMasks);

                    if (layerIndex1 != layerIndex2)
                    {
                        return((layerIndex1 < layerIndex2) ? hit1 : hit2);
                    }
                }

                // Check which hit is closer.
                return(hit1.RayDistance < hit2.RayDistance ? hit1 : hit2);
            }

            return(hit1.HitObject != null ? hit1 : hit2);
        }
예제 #4
0
        /// <summary>
        /// Perform a Unity Graphics Raycast to determine which uGUI element is currently being gazed at, if any.
        /// </summary>
        /// <param name="pointer"></param>
        /// <param name="graphicEventData"></param>
        /// <param name="prioritizedLayerMasks"></param>
        /// <param name="hitResult"></param>
        private void RaycastGraphics(IMixedRealityPointer pointer, PointerEventData graphicEventData, LayerMask[] prioritizedLayerMasks, PointerHitResult hitResult)
        {
            Debug.Assert(UIRaycastCamera != null, "Missing UIRaycastCamera!");

            if (!UIRaycastCamera.nearClipPlane.Equals(0.01f))
            {
                UIRaycastCamera.nearClipPlane = 0.01f;
            }

            if (pointer.Rays == null)
            {
                Debug.LogError($"No valid rays for {pointer.PointerName} pointer.");
                return;
            }

            if (pointer.Rays.Length <= 0)
            {
                Debug.LogError($"No valid rays for {pointer.PointerName} pointer");
                return;
            }

            // Cast rays for every step until we score a hit
            float totalDistance = 0.0f;

            for (int i = 0; i < pointer.Rays.Length; i++)
            {
                if (RaycastGraphicsStep(graphicEventData, pointer.Rays[i], prioritizedLayerMasks, out var raycastResult))
                {
                    if (raycastResult.isValid &&
                        raycastResult.distance < pointer.Rays[i].Length &&
                        raycastResult.module != null &&
                        raycastResult.module.eventCamera == UIRaycastCamera)
                    {
                        totalDistance += raycastResult.distance;

                        newUiRaycastPosition.x = raycastResult.screenPosition.x;
                        newUiRaycastPosition.y = raycastResult.screenPosition.y;
                        newUiRaycastPosition.z = raycastResult.distance;

                        var worldPos = UIRaycastCamera.ScreenToWorldPoint(newUiRaycastPosition);
                        var normal   = -raycastResult.gameObject.transform.forward;

                        hitResult.Set(raycastResult, worldPos, normal, pointer.Rays[i], i, totalDistance);
                        return;
                    }
                }

                totalDistance += pointer.Rays[i].Length;
            }
        }
예제 #5
0
        private static void UpdatePointerRayOnHit(RayStep[] raySteps, RaycastHit physicsHit, int hitRayIndex, float rayStartDistance, PointerHitResult hitResult)
        {
            var origin   = raySteps[hitRayIndex].Origin;
            var terminus = physicsHit.point;

            raySteps[hitRayIndex].UpdateRayStep(ref origin, ref terminus);
            hitResult.Set(physicsHit, raySteps[hitRayIndex], hitRayIndex, rayStartDistance + physicsHit.distance);
        }
예제 #6
0
        /// <summary>
        /// Perform a Unity physics Raycast to determine which scene objects with a collider is currently being gazed at, if any.
        /// </summary>
        /// <param name="pointer"></param>
        /// <param name="prioritizedLayerMasks"></param>
        /// <param name="hitResult"></param>
        private static void RaycastPhysics(IMixedRealityPointer pointer, LayerMask[] prioritizedLayerMasks, PointerHitResult hitResult)
        {
            float rayStartDistance = 0;
            var   pointerRays      = pointer.Rays;

            if (pointerRays == null)
            {
                Debug.LogError($"No valid rays for {pointer.PointerName} pointer.");
                return;
            }

            if (pointerRays.Length <= 0)
            {
                Debug.LogError($"No valid rays for {pointer.PointerName} pointer");
                return;
            }

            // Check raycast for each step in the pointing source
            for (int i = 0; i < pointerRays.Length; i++)
            {
                switch (pointer.RaycastMode)
                {
                case RaycastMode.Simple:
                    if (MixedRealityRaycaster.RaycastSimplePhysicsStep(pointerRays[i], prioritizedLayerMasks, out var simplePhysicsHit))
                    {
                        // Set the pointer source's origin ray to this step
                        UpdatePointerRayOnHit(pointerRays, simplePhysicsHit, i, rayStartDistance, hitResult);
                        return;
                    }
                    break;

                case RaycastMode.Box:
                    Debug.LogWarning("Box Raycasting Mode not supported for pointers.");
                    break;

                case RaycastMode.Sphere:
                    if (MixedRealityRaycaster.RaycastSpherePhysicsStep(pointerRays[i], pointer.SphereCastRadius, prioritizedLayerMasks, out var spherePhysicsHit))
                    {
                        // Set the pointer source's origin ray to this step
                        UpdatePointerRayOnHit(pointerRays, spherePhysicsHit, i, rayStartDistance, hitResult);
                        return;
                    }
                    break;

                // TODO SphereOverlap
                default:
                    Debug.LogError($"Invalid raycast mode {pointer.RaycastMode} for {pointer.PointerName} pointer.");
                    break;
                }

                rayStartDistance += pointer.Rays[i].Length;
            }
        }
        /// <summary>
        /// Perform a scene query to determine which scene objects with a collider is currently being gazed at, if any.
        /// </summary>
        /// <param name="pointerData"></param>
        /// <param name="prioritizedLayerMasks"></param>
        private static void QueryScene(IMixedRealityPointer pointer, LayerMask[] prioritizedLayerMasks, PointerHitResult hit)
        {
            float      rayStartDistance = 0;
            RaycastHit physicsHit;

            RayStep[] pointerRays = pointer.Rays;

            if (pointerRays == null)
            {
                Debug.LogError($"No valid rays for {pointer.PointerName} pointer.");
                return;
            }

            if (pointerRays.Length <= 0)
            {
                Debug.LogError($"No valid rays for {pointer.PointerName} pointer");
                return;
            }

            // Perform query for each step in the pointing source
            for (int i = 0; i < pointerRays.Length; i++)
            {
                switch (pointer.SceneQueryType)
                {
                case SceneQueryType.SimpleRaycast:
                    if (MixedRealityRaycaster.RaycastSimplePhysicsStep(pointerRays[i], prioritizedLayerMasks, out physicsHit))
                    {
                        UpdatePointerRayOnHit(pointerRays, physicsHit, i, rayStartDistance, hit);
                        return;
                    }
                    break;

                case SceneQueryType.BoxRaycast:
                    Debug.LogWarning("Box Raycasting Mode not supported for pointers.");
                    break;

                case SceneQueryType.SphereCast:
                    if (MixedRealityRaycaster.RaycastSpherePhysicsStep(pointerRays[i], pointer.SphereCastRadius, prioritizedLayerMasks, out physicsHit))
                    {
                        UpdatePointerRayOnHit(pointerRays, physicsHit, i, rayStartDistance, hit);
                        return;
                    }
                    break;

                case SceneQueryType.SphereOverlap:
                    Collider[] colliders = UnityEngine.Physics.OverlapSphere(pointer.Rays[i].Origin, pointer.SphereCastRadius, ~UnityEngine.Physics.IgnoreRaycastLayer);

                    if (colliders.Length > 0)
                    {
                        Vector3    testPoint       = pointer.Rays[i].Origin;
                        GameObject closest         = null;
                        float      closestDistance = Mathf.Infinity;
                        Vector3    objectHitPoint  = testPoint;

                        foreach (Collider collider in colliders)
                        {
                            // Policy: in order for an collider to be near interactable it must have
                            // a NearInteractionGrabbable component on it.
                            // FIXME: This is assuming only the grab pointer is using SceneQueryType.SphereOverlap,
                            //        but there may be other pointers using the same query type which have different semantics.
                            if (collider.GetComponent <NearInteractionGrabbable>() == null)
                            {
                                continue;
                            }
                            // From https://docs.unity3d.com/ScriptReference/Collider.ClosestPoint.html
                            // If location is in the collider the closestPoint will be inside.
                            Vector3 closestPointToCollider = collider.ClosestPoint(testPoint);
                            float   distance = (testPoint - closestPointToCollider).sqrMagnitude;
                            if (distance < closestDistance)
                            {
                                closestDistance = distance;
                                closest         = collider.gameObject;
                                objectHitPoint  = closestPointToCollider;
                            }
                        }
                        if (closest != null)
                        {
                            hit.Set(closest, objectHitPoint, Vector3.zero, pointer.Rays[i], 0, closestDistance);
                            return;
                        }
                    }
                    break;

                default:
                    Debug.LogError($"Invalid raycast mode {pointer.SceneQueryType} for {pointer.PointerName} pointer.");
                    break;
                }

                rayStartDistance += pointer.Rays[i].Length;
            }
        }
예제 #8
0
            public void UpdateHit(PointerHitResult hitResult, GameObject syncTarget)
            {
                focusDetails.LastRaycastHit            = hitResult.RaycastHit;
                focusDetails.LastGraphicsRaycastResult = hitResult.GraphicsRaycastResult;

                if (hitResult.RayStepIndex >= 0)
                {
                    RayStepIndex = hitResult.RayStepIndex;
                    StartPoint   = hitResult.Ray.Origin;

                    focusDetails.RayDistance = hitResult.RayDistance;
                    focusDetails.EndPoint    = hitResult.HitPointOnObject;
                    focusDetails.Normal      = hitResult.HitNormalOnObject;
                }
                else
                {
                    // If we don't have a valid ray cast, use the whole pointer ray.focusDetails.EndPoint
                    var firstStep = Pointer.Rays[0];
                    var finalStep = Pointer.Rays[Pointer.Rays.Length - 1];
                    RayStepIndex = 0;

                    StartPoint = firstStep.Origin;

                    var rayDistance = 0.0f;

                    for (int i = 0; i < Pointer.Rays.Length; i++)
                    {
                        rayDistance += Pointer.Rays[i].Length;
                    }

                    focusDetails.RayDistance = rayDistance;
                    focusDetails.EndPoint    = finalStep.Terminus;
                    focusDetails.Normal      = -finalStep.Direction;
                }

                Direction    = focusDetails.EndPoint - lastPosition;
                lastPosition = focusDetails.EndPoint;

                focusDetails.HitObject = hitResult.HitObject;

                if (syncTarget != null)
                {
                    if (syncedPointerTarget == null && CurrentPointerTarget != null && CurrentPointerTarget == syncTarget)
                    {
                        Debug.Assert(CurrentPointerTarget != null, "No Sync Target Set!");

                        syncedPointerTarget = CurrentPointerTarget;

                        prevPhysicsLayer = CurrentPointerTarget.layer;
                        Debug.Assert(prevPhysicsLayer != IgnoreRaycastLayer, $"Failed to get a valid raycast layer for {syncedPointerTarget.name}: {LayerMask.LayerToName(prevPhysicsLayer)}");
                        CurrentPointerTarget.SetLayerRecursively(IgnoreRaycastLayer);

                        var grabPoint = Pointer.OverrideGrabPoint ?? focusDetails.EndPoint;

                        if (grabPoint == Vector3.zero)
                        {
                            GrabPoint           = CurrentPointerTarget.transform.TransformPoint(grabPoint);
                            GrabPointLocalSpace = CurrentPointerTarget.transform.InverseTransformPoint(GrabPoint);
                        }
                        else
                        {
                            GrabPoint           = grabPoint;
                            GrabPointLocalSpace = CurrentPointerTarget.transform.InverseTransformPoint(GrabPoint);
                        }
                    }
                    else if (syncTarget != CurrentPointerTarget)
                    {
                        GetCurrentTarget();
                    }

                    if (syncedPointerTarget != null)
                    {
                        GrabPoint           = CurrentPointerTarget.transform.TransformPoint(GrabPointLocalSpace);
                        GrabPointLocalSpace = CurrentPointerTarget.transform.InverseTransformPoint(GrabPoint);

                        // Visualize the relevant points and their relation
                        if (Application.isEditor && MixedRealityRaycaster.DebugEnabled)
                        {
                            DebugUtilities.DrawPoint(GrabPoint, Color.red);
                            DebugUtilities.DrawPoint(focusDetails.EndPoint, Color.yellow);

                            Debug.DrawLine(focusDetails.EndPoint, GrabPoint, Color.magenta);

                            var currentPosition = CurrentPointerTarget.transform.position;
                            var targetPosition  = (focusDetails.EndPoint + currentPosition) - GrabPoint;

                            Debug.DrawLine(GrabPoint, currentPosition, Color.magenta);
                            Debug.DrawLine(currentPosition, GrabPoint, Color.magenta);
                            DebugUtilities.DrawPoint(currentPosition, Color.cyan);
                            DebugUtilities.DrawPoint(targetPosition, Color.blue);

                            Debug.DrawLine(targetPosition, currentPosition, Color.blue);
                        }
                    }
                }
                else
                {
                    GetCurrentTarget();
                }

                void GetCurrentTarget()
                {
                    if (syncedPointerTarget != null)
                    {
                        syncedPointerTarget.SetLayerRecursively(prevPhysicsLayer);
                        syncedPointerTarget = null;
                    }

                    PreviousPointerTarget     = CurrentPointerTarget;
                    CurrentPointerTarget      = focusDetails.HitObject;
                    Pointer.OverrideGrabPoint = null;
                    GrabPoint           = Vector3.zero;
                    GrabPointLocalSpace = Vector3.zero;
                }

                if (CurrentPointerTarget != null)
                {
                    focusDetails.EndPointLocalSpace = CurrentPointerTarget.transform.InverseTransformPoint(focusDetails.EndPoint);
                    focusDetails.NormalLocalSpace   = CurrentPointerTarget.transform.InverseTransformDirection(focusDetails.Normal);
                }
                else
                {
                    focusDetails.EndPointLocalSpace = Vector3.zero;
                    focusDetails.NormalLocalSpace   = Vector3.zero;
                }
            }