/// <summary> /// Calculates the best position to place an object in AR based on screen position. /// Could be used for tapping a location on the screen, dragging an object, or using a fixed /// cursor in the center of the screen for placing and moving objects. /// /// Objects are placed along the x/z of the grounding plane. When placed on an AR plane /// below the grounding plane, the object will drop straight down onto it in world space. /// This prevents the object from being pushed deeper into the scene when moving from a /// higher plane to a lower plane. When moving from a lower plane to a higher plane, this /// function returns a new groundingPlane to replace the old one. /// </summary> /// <returns>The best placement position.</returns> /// <param name="currentAnchorPosition">Position of the parent anchor, i.e., where the /// object is before translation starts.</param> /// <param name="screenPos">Location on the screen in pixels to place the object at.</param> /// <param name="groundingPlaneHeight">The starting height of the plane that the object is /// being placed along.</param> /// <param name="hoverOffset">How much should the object hover above the groundingPlane /// before it has been placed.</param> /// <param name="maxTranslationDistance">The maximum distance that the object can be /// translated.</param> /// <param name="gestureTranslationMode">The translation mode, indicating the plane types allowed. /// </param> public static Placement GetBestPlacementPosition( Vector3 currentAnchorPosition, Vector2 screenPos, float groundingPlaneHeight, float hoverOffset, float maxTranslationDistance, GestureTranslationMode gestureTranslationMode) { Placement result = new Placement(); if (!CheckDependentManagers()) { return(result); } result.UpdatedGroundingPlaneHeight = groundingPlaneHeight; // Get the angle between the camera and the object's down direction. float angle = Vector3.Angle(Camera.main.transform.forward, Vector3.down); angle = 90.0f - angle; float touchOffsetRatio = Mathf.Clamp01(angle / 90.0f); float screenTouchOffset = touchOffsetRatio * k_MaxScreenTouchOffset; screenPos.y += GestureTouchesUtility.InchesToPixels(screenTouchOffset); float hoverRatio = Mathf.Clamp01(angle / 45.0f); hoverOffset *= hoverRatio; float distance = (Camera.main.transform.position - currentAnchorPosition).magnitude; float distanceHoverRatio = Mathf.Clamp01(distance / k_HoverDistanceThreshold); hoverOffset *= distanceHoverRatio; // The best estimate of the point in the plane where the object will be placed: Vector3 groundingPoint; // Get the ray to cast into the scene from the perspective of the camera. if (Raycast(new Vector2(screenPos.x, screenPos.y), s_Hits, TrackableType.Planes)) { var firstHit = s_Hits[0]; var plane = s_ARPlaneManager.GetPlane(firstHit.trackableId); if (plane == null || IsPlaneTypeAllowed(gestureTranslationMode, plane.alignment)) { // Avoid detecting the back of existing planes. if (Vector3.Dot(Camera.main.transform.position - firstHit.pose.position, firstHit.pose.rotation * Vector3.up) < 0) { return(result); } // Don't allow hovering for vertical or horizontal downward facing planes. if (plane == null || (plane.alignment == PlaneAlignment.Vertical || plane.alignment == PlaneAlignment.HorizontalDown || plane.alignment == PlaneAlignment.HorizontalUp)) { groundingPoint = LimitTranslation( firstHit.pose.position, currentAnchorPosition, maxTranslationDistance); if (plane != null) { result.PlacementPlane = plane; result.HasPlane = true; } result.HasPlacementPosition = true; result.PlacementPosition = groundingPoint; result.HasHoveringPosition = true; result.HoveringPosition = groundingPoint; result.UpdatedGroundingPlaneHeight = groundingPoint.y; result.PlacementRotation = firstHit.pose.rotation; return(result); } } else { // Plane type not allowed. return(result); } } // Return early if the camera is pointing upwards. if (angle < 0f) { return(result); } // If the grounding point is lower than the current grounding plane height, or if the // raycast did not return a hit, then we extend the grounding plane to infinity, and do // a new raycast into the scene from the perspective of the camera. Ray cameraRay = Camera.main.ScreenPointToRay(screenPos); Plane groundingPlane = new Plane(Vector3.up, new Vector3(0.0f, groundingPlaneHeight, 0.0f)); // Find the hovering position by casting from the camera onto the grounding plane // and offsetting the result by the hover offset. float enter; if (groundingPlane.Raycast(cameraRay, out enter)) { groundingPoint = LimitTranslation( cameraRay.GetPoint(enter), currentAnchorPosition, maxTranslationDistance); result.HasHoveringPosition = true; result.HoveringPosition = groundingPoint + (Vector3.up * hoverOffset); } else { // If we can't successfully cast onto the groundingPlane, just return early. return(result); } return(result); }
/// <summary> /// Calculates the best position to place an object in AR based on screen position. /// Could be used for tapping a location on the screen, dragging an object, or using a fixed /// cursor in the center of the screen for placing and moving objects. /// </summary> /// <returns>Returns the best placement position.</returns> /// <param name="currentAnchorPosition">Position of the parent anchor, i.e., where the /// object is before translation starts.</param> /// <param name="screenPosition">Location on the screen in pixels to place the object at.</param> /// <param name="groundingPlaneHeight">The starting height of the plane that the object is /// being placed along.</param> /// <param name="hoverOffset">How much should the object hover above the groundingPlane /// before it has been placed.</param> /// <param name="maxTranslationDistance">The maximum distance that the object can be /// translated.</param> /// <param name="gestureTranslationMode">The translation mode, indicating the plane types allowed. /// </param> /// <param name="sessionOrigin">The <see cref="ARSessionOrigin"/> used for raycasting.</param> /// <param name="trackableTypes">(Optional) The types of trackables to cast against.</param> /// <param name="fallbackLayerMask">(Optional) The <see cref="LayerMask"/> that is used during /// an additional raycast when no trackables are hit. Defaults to Nothing which skips the fallback raycast.</param> /// <remarks> /// Objects are placed along the x/z of the grounding plane. When placed on an AR plane /// below the grounding plane, the object will drop straight down onto it in world space. /// This prevents the object from being pushed deeper into the scene when moving from a /// higher plane to a lower plane. When moving from a lower plane to a higher plane, this /// function returns a new groundingPlane to replace the old one. /// </remarks> public static Placement GetBestPlacementPosition( Vector3 currentAnchorPosition, Vector2 screenPosition, float groundingPlaneHeight, float hoverOffset, float maxTranslationDistance, GestureTranslationMode gestureTranslationMode, ARSessionOrigin sessionOrigin, TrackableType trackableTypes = TrackableType.PlaneWithinPolygon, int fallbackLayerMask = 0) { var result = new Placement(); if (sessionOrigin == null) { TryGetSessionOrigin(out sessionOrigin); } var camera = sessionOrigin != null ? sessionOrigin.camera : Camera.main; if (camera == null) { return(result); } var cameraTransform = camera.transform; result.updatedGroundingPlaneHeight = groundingPlaneHeight; // Get the angle between the camera and the object's down direction. var angle = 90f - Vector3.Angle(cameraTransform.forward, Vector3.down); var touchOffsetRatio = Mathf.Clamp01(angle / 90f); var screenTouchOffset = touchOffsetRatio * k_MaxScreenTouchOffset; screenPosition.y += GestureTouchesUtility.InchesToPixels(screenTouchOffset); var hoverRatio = Mathf.Clamp01(angle / 45f); hoverOffset *= hoverRatio; var distance = (cameraTransform.position - currentAnchorPosition).magnitude; var distanceHoverRatio = Mathf.Clamp01(distance / k_HoverDistanceThreshold); hoverOffset *= distanceHoverRatio; // The best estimate of the point in the plane where the object will be placed: Vector3 groundingPoint; // Get the ray to cast into the scene from the perspective of the camera. if (Raycast(screenPosition, s_Hits, sessionOrigin, trackableTypes, fallbackLayerMask)) { if (!TryGetTrackableManager(sessionOrigin, out ARPlaneManager planeManager)) { return(result); } var firstHit = s_Hits[0]; var plane = planeManager.GetPlane(firstHit.trackableId); if (plane == null || IsPlaneTypeAllowed(gestureTranslationMode, plane.alignment)) { // Avoid detecting the back of existing planes. if (Vector3.Dot(cameraTransform.position - firstHit.pose.position, firstHit.pose.rotation * Vector3.up) < 0f) { return(result); } // Don't allow hovering for vertical or horizontal downward facing planes. if (plane == null || plane.alignment == PlaneAlignment.Vertical || plane.alignment == PlaneAlignment.HorizontalDown || plane.alignment == PlaneAlignment.HorizontalUp) { groundingPoint = LimitTranslation( firstHit.pose.position, currentAnchorPosition, maxTranslationDistance); if (plane != null) { result.placementPlane = plane; result.hasPlane = true; } result.hasPlacementPosition = true; result.placementPosition = groundingPoint; result.hasHoveringPosition = true; result.hoveringPosition = groundingPoint; result.updatedGroundingPlaneHeight = groundingPoint.y; result.placementRotation = firstHit.pose.rotation; return(result); } } else { // Plane type not allowed. return(result); } } // Return early if the camera is pointing upwards. if (angle < 0f) { return(result); } // If the grounding point is lower than the current grounding plane height, or if the // raycast did not return a hit, then we extend the grounding plane to infinity, and do // a new raycast into the scene from the perspective of the camera. var cameraRay = camera.ScreenPointToRay(screenPosition); var groundingPlane = new Plane(Vector3.up, new Vector3(0f, groundingPlaneHeight, 0f)); // Find the hovering position by casting from the camera onto the grounding plane // and offsetting the result by the hover offset. if (groundingPlane.Raycast(cameraRay, out var enter)) { groundingPoint = LimitTranslation( cameraRay.GetPoint(enter), currentAnchorPosition, maxTranslationDistance); result.hasHoveringPosition = true; result.hoveringPosition = groundingPoint + (Vector3.up * hoverOffset); } else { // If we can't successfully cast onto the groundingPlane, just return early. return(result); } return(result); }