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(); }
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; } }
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); }
/// <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; } }
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); }
/// <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; } }
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; } }