/// <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);
        }
Esempio n. 2
0
        /// <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);
        }