using Core; using UnityEngine; namespace Utils { /// /// Utility methods for screen space and UI coordinate conversions /// public static class ScreenSpaceUtility { /// /// Convert RectTransform to screen space rect with center position. /// Useful for screenshot capture or screen-based calculations. /// /// RectTransform to convert /// Camera used for coordinate conversion (null = Camera.main) /// If true, clamps the rect to visible screen area /// If true, returns center position; if false, returns bottom-left position /// Screen space rect (position is center or bottom-left based on returnCenterPosition) public static Rect RectTransformToScreenRect( RectTransform rectTransform, Camera camera = null, bool clampToScreenBounds = true, bool returnCenterPosition = true) { if (rectTransform == null) { Logging.Error("[ScreenSpaceUtility] RectTransform is null!"); return new Rect(Screen.width / 2f, Screen.height / 2f, 0, 0); } if (camera == null) camera = Camera.main; // Get world corners (0=bottom-left, 1=top-left, 2=top-right, 3=bottom-right) Vector3[] corners = new Vector3[4]; rectTransform.GetWorldCorners(corners); // Determine correct camera based on Canvas render mode Camera canvasCamera = GetCanvasCamera(rectTransform, camera); // Convert corners to screen space Vector2 min = RectTransformUtility.WorldToScreenPoint(canvasCamera, corners[0]); Vector2 max = RectTransformUtility.WorldToScreenPoint(canvasCamera, corners[2]); Logging.Debug($"[ScreenSpaceUtility] Canvas mode: {rectTransform.GetComponentInParent()?.renderMode}, Camera: {canvasCamera?.name ?? "null"}"); Logging.Debug($"[ScreenSpaceUtility] Raw screen coords - min: {min}, max: {max}"); // Apply screen bounds clamping if requested if (clampToScreenBounds) { return ClampRectToScreenBounds(min, max, returnCenterPosition); } else { // No clamping - use raw values float width = Mathf.Abs(max.x - min.x); float height = Mathf.Abs(max.y - min.y); if (returnCenterPosition) { float centerX = min.x + width / 2f; float centerY = min.y + height / 2f; return new Rect(centerX, centerY, width, height); } else { return new Rect(min.x, min.y, width, height); } } } /// /// Get the appropriate camera for UI coordinate conversion based on Canvas render mode /// /// RectTransform to find canvas for /// Fallback camera if no canvas found /// Camera to use for coordinate conversion (null for Overlay mode) public static Camera GetCanvasCamera(RectTransform rectTransform, Camera fallbackCamera = null) { Canvas canvas = rectTransform.GetComponentInParent(); if (canvas == null) { return fallbackCamera; } switch (canvas.renderMode) { case RenderMode.ScreenSpaceOverlay: // For Screen Space - Overlay, use null (direct pixel coords) return null; case RenderMode.ScreenSpaceCamera: // For Screen Space - Camera, use the canvas's worldCamera return canvas.worldCamera; case RenderMode.WorldSpace: // For World Space, use canvas camera or fallback return canvas.worldCamera ?? fallbackCamera; default: return fallbackCamera; } } /// /// Clamp rect coordinates to screen bounds and calculate final rect /// /// Bottom-left corner in screen space /// Top-right corner in screen space /// If true, returns center position; if false, returns bottom-left /// Clamped screen rect private static Rect ClampRectToScreenBounds(Vector2 min, Vector2 max, bool returnCenterPosition) { // Clamp to screen bounds float minX = Mathf.Max(0, min.x); float minY = Mathf.Max(0, min.y); float maxX = Mathf.Min(Screen.width, max.x); float maxY = Mathf.Min(Screen.height, max.y); // Check if rect is completely outside screen bounds if (minX >= Screen.width || minY >= Screen.height || maxX <= 0 || maxY <= 0) { Logging.Warning("[ScreenSpaceUtility] RectTransform is completely outside screen bounds!"); return new Rect(Screen.width / 2f, Screen.height / 2f, 0, 0); } // Calculate dimensions from clamped bounds float width = maxX - minX; float height = maxY - minY; // Validate dimensions if (width <= 0 || height <= 0) { Logging.Warning($"[ScreenSpaceUtility] Invalid dimensions after clamping: {width}x{height}"); return new Rect(Screen.width / 2f, Screen.height / 2f, 100, 100); // Fallback small rect } Logging.Debug($"[ScreenSpaceUtility] Clamped bounds - min: ({minX}, {minY}), max: ({maxX}, {maxY})"); if (returnCenterPosition) { // Calculate center position from clamped bounds float centerX = minX + width / 2f; float centerY = minY + height / 2f; Logging.Debug($"[ScreenSpaceUtility] Final rect - center: ({centerX}, {centerY}), size: ({width}, {height})"); return new Rect(centerX, centerY, width, height); } else { // Return bottom-left position Logging.Debug($"[ScreenSpaceUtility] Final rect - position: ({minX}, {minY}), size: ({width}, {height})"); return new Rect(minX, minY, width, height); } } /// /// Check if a screen rect is completely outside screen bounds /// public static bool IsRectOutsideScreenBounds(Rect rect) { return rect.xMax < 0 || rect.xMin > Screen.width || rect.yMax < 0 || rect.yMin > Screen.height; } /// /// Get the visible portion of a rect when clamped to screen bounds /// public static Rect GetVisibleScreenRect(Rect rect) { float minX = Mathf.Max(0, rect.xMin); float minY = Mathf.Max(0, rect.yMin); float maxX = Mathf.Min(Screen.width, rect.xMax); float maxY = Mathf.Min(Screen.height, rect.yMax); float width = Mathf.Max(0, maxX - minX); float height = Mathf.Max(0, maxY - minY); return new Rect(minX, minY, width, height); } /// /// Calculate the percentage of a rect that is visible on screen /// public static float GetVisiblePercentage(Rect rect) { if (rect.width <= 0 || rect.height <= 0) return 0f; Rect visibleRect = GetVisibleScreenRect(rect); float totalArea = rect.width * rect.height; float visibleArea = visibleRect.width * visibleRect.height; return visibleArea / totalArea; } } }