// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Utilities; using UnityEngine; using UnityEngine.UI; namespace Microsoft.MixedReality.Toolkit { /// /// Extensions for the Canvas class. /// public static class CanvasExtensions { /// /// Convenience method for getting a plane for this canvas in world coordinates. /// /// The canvas to get the plane from. /// A Plane for this canvas. public static Plane GetPlane(this Canvas canvas) { Vector3[] corners = canvas.GetWorldCorners(); // Now set a plane from any of the 3 corners (clockwise) so that we can compute our gaze intersection Plane plane = new Plane(corners[0], corners[1], corners[2]); return plane; } /// /// Convenience method for getting the corners of the canvas in world coordinates. Ordered clockwise from bottom-left. /// /// The canvas to get the world corners from. /// An array of Vector3s that represent the corners of the canvas in world coordinates. public static Vector3[] GetWorldCorners(this Canvas canvas) { Vector3[] worldCorners = new Vector3[4]; RectTransform rect = canvas.GetComponent(); rect.GetWorldCorners(worldCorners); return worldCorners; } /// /// Convenience method for getting the corners of the canvas in local coordinates. Ordered clockwise from bottom-left. /// /// The canvas to get the local corners from. /// An array of Vector3s that represent the corners of the canvas in local coordinates. public static Vector3[] GetLocalCorners(this Canvas canvas) { Vector3[] localCorners = new Vector3[4]; RectTransform rect = canvas.GetComponent(); rect.GetLocalCorners(localCorners); return localCorners; } /// /// Convenience method for getting the corners of the canvas in viewport coordinates. Note /// that the points have the same ordering as the array returned in GetWorldCorners() /// /// The canvas to get the viewport corners from /// An array of Vector3s that represent the corners of the canvas in viewport coordinates public static Vector3[] GetViewportCorners(this Canvas canvas) { Vector3[] viewportCorners = new Vector3[4]; Vector3[] worldCorners = canvas.GetWorldCorners(); for (int i = 0; i < 4; i++) { viewportCorners[i] = CameraCache.Main.WorldToViewportPoint(worldCorners[i]); } return viewportCorners; } /// /// Gets the position of the corners for a canvas in screen space. /// 1 -- 2 /// | | /// 0 -- 3 /// /// The canvas to get the screen corners for. public static Vector3[] GetScreenCorners(this Canvas canvas) { Vector3[] screenCorners = new Vector3[4]; Vector3[] worldCorners = canvas.GetWorldCorners(); for (int i = 0; i < 4; i++) { screenCorners[i] = CameraCache.Main.WorldToScreenPoint(worldCorners[i]); } return screenCorners; } /// /// Returns a rectangle in screen coordinates that encompasses the bounds of the target canvas. /// /// The canvas the get the screen rect for public static Rect GetScreenRect(this Canvas canvas) { Vector3[] screenCorners = canvas.GetScreenCorners(); float x = Mathf.Min(screenCorners[0].x, screenCorners[1].x); float y = Mathf.Min(screenCorners[0].y, screenCorners[3].y); float xMax = Mathf.Max(screenCorners[2].x, screenCorners[3].x); float yMax = Mathf.Max(screenCorners[1].y, screenCorners[2].y); return new Rect(x, y, xMax - x, yMax - y); } /// /// Raycast against a canvas using a ray. /// /// The canvas to raycast against /// The origin of the ray /// The direction of the ray /// The distance of the ray /// The hitpoint of the ray /// The child object that was hit or the canvas itself if it has no active children that were within the hit range. public static bool Raycast(this Canvas canvas, Vector3 rayOrigin, Vector3 rayDirection, out float distance, out Vector3 hitPoint, out GameObject hitChildObject) { hitChildObject = null; Plane plane = canvas.GetPlane(); Ray ray = new Ray(rayOrigin, rayDirection); if (plane.Raycast(ray, out distance)) { // See if the point lies within the local canvas rect of the plane Vector3[] corners = canvas.GetLocalCorners(); hitPoint = rayOrigin + (rayDirection.normalized * distance); Vector3 localHitPoint = canvas.transform.InverseTransformPoint(hitPoint); if (localHitPoint.x >= corners[0].x && localHitPoint.x <= corners[3].x && localHitPoint.y <= corners[2].y && localHitPoint.y >= corners[3].y) { hitChildObject = canvas.gameObject; // look for the child object that was hit RectTransform rectTransform = GetChildRectTransformAtPoint(canvas.GetComponent(), hitPoint, true, true, true); if (rectTransform != null) { hitChildObject = rectTransform.gameObject; } else { hitChildObject = canvas.gameObject; } return true; } } hitPoint = Vector3.zero; return false; } /// /// Gets a child rect transform for the given point and parameters. /// /// The rect transform to look for children that may contain the projected (orthogonal to the child's normal) world point /// The world point /// Indicates if the check should be done recursively /// If true, will only check children that are active, otherwise it will check all children. /// If true, will only check children that if they have a graphic and have its member raycastTarget set to true, otherwise will ignore the raycastTarget value. Will still allow children to be checked that do not have a graphic component. public static RectTransform GetChildRectTransformAtPoint(this RectTransform rectTransformParent, Vector3 worldPoint, bool recursive, bool shouldReturnActive, bool shouldReturnRaycastable) { Vector3[] localCorners = new Vector3[4]; Vector3 childLocalPoint; RectTransform rectTransform; bool shouldRaycast = false; for (int i = rectTransformParent.childCount - 1; i >= 0; i--) { rectTransform = rectTransformParent.GetChild(i).GetComponent(); Graphic graphic = rectTransform.GetComponent(); shouldRaycast = ((shouldReturnRaycastable && graphic != null && graphic.raycastTarget) || graphic == null || !shouldReturnRaycastable); if (((shouldReturnActive && rectTransform.gameObject.activeSelf) || !shouldReturnActive)) { rectTransform.GetLocalCorners(localCorners); childLocalPoint = rectTransform.InverseTransformPoint(worldPoint); if (recursive) { RectTransform childRect = GetChildRectTransformAtPoint(rectTransform, worldPoint, recursive, shouldReturnActive, shouldReturnRaycastable); if (childRect != null) { return childRect; } } if (shouldRaycast && childLocalPoint.x >= localCorners[0].x && childLocalPoint.x <= localCorners[3].x && childLocalPoint.y <= localCorners[2].y && childLocalPoint.y >= localCorners[3].y) { return rectTransform; } } } return null; } } }