VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Runtime/UI/CanvasOptimizer.cs
2025-07-29 13:45:50 +03:00

439 lines
17 KiB
C#

using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.XR.Interaction.Toolkit.Utilities;
namespace UnityEngine.XR.Interaction.Toolkit.UI
{
/// <summary>
/// Keeps track of canvases in a scene and optimizes them by removing unnecessary components in nested canvases and canvases out of view.
/// </summary>
[AddComponentMenu("Event/Canvas Optimizer", 11)]
[HelpURL(XRHelpURLConstants.k_CanvasOptimizer)]
public class CanvasOptimizer : MonoBehaviour
{
class CanvasState
{
const float k_CanvasCheckInterval = 0.5f;
class CanvasSettings
{
public bool present { get; set; }
AdditionalCanvasShaderChannels m_AdditionalShaderChannels;
float m_NormalizedSortingGridSize;
bool m_OverridePixelPerfect;
bool m_OverrideSorting;
float m_PlaneDistance;
float m_ReferencePixelsPerUnit;
RenderMode m_RenderMode;
float m_ScaleFactor;
int m_SortingLayerID;
string m_SortingLayerName;
int m_SortingOrder;
int m_TargetDisplay;
public void CopyFrom(Canvas source)
{
m_AdditionalShaderChannels = source.additionalShaderChannels;
m_NormalizedSortingGridSize = source.normalizedSortingGridSize;
m_OverridePixelPerfect = source.overridePixelPerfect;
m_OverrideSorting = source.overrideSorting;
m_PlaneDistance = source.planeDistance;
m_ReferencePixelsPerUnit = source.referencePixelsPerUnit;
m_RenderMode = source.renderMode;
m_ScaleFactor = source.scaleFactor;
m_SortingLayerID = source.sortingLayerID;
m_SortingLayerName = source.sortingLayerName;
m_SortingOrder = source.sortingOrder;
m_TargetDisplay = source.targetDisplay;
}
public void CopyTo(Canvas dest)
{
dest.additionalShaderChannels = m_AdditionalShaderChannels;
dest.normalizedSortingGridSize = m_NormalizedSortingGridSize;
dest.overridePixelPerfect = m_OverridePixelPerfect;
dest.overrideSorting = m_OverrideSorting;
dest.planeDistance = m_PlaneDistance;
dest.referencePixelsPerUnit = m_ReferencePixelsPerUnit;
dest.renderMode = m_RenderMode;
dest.scaleFactor = m_ScaleFactor;
dest.sortingLayerID = m_SortingLayerID;
dest.sortingLayerName = m_SortingLayerName;
dest.sortingOrder = m_SortingOrder;
dest.targetDisplay = m_TargetDisplay;
}
}
class CanvasScalerSettings
{
public bool present { get; set; }
float m_DefaultSpriteDPI;
float m_DynamicPixelsPerUnit;
float m_FallbackScreenDPI;
float m_MatchWidthOrHeight;
CanvasScaler.Unit m_PhysicalUnit;
float m_ReferencePixelsPerUnit;
Vector2 m_ReferenceResolution;
float m_ScaleFactor;
CanvasScaler.ScreenMatchMode m_ScreenMatchMode;
CanvasScaler.ScaleMode m_UiScaleMode;
public void CopyFrom(CanvasScaler source)
{
m_DefaultSpriteDPI = source.defaultSpriteDPI;
m_DynamicPixelsPerUnit = source.dynamicPixelsPerUnit;
m_FallbackScreenDPI = source.fallbackScreenDPI;
m_MatchWidthOrHeight = source.matchWidthOrHeight;
m_PhysicalUnit = source.physicalUnit;
m_ReferencePixelsPerUnit = source.referencePixelsPerUnit;
m_ReferenceResolution = source.referenceResolution;
m_ScaleFactor = source.scaleFactor;
m_ScreenMatchMode = source.screenMatchMode;
m_UiScaleMode = source.uiScaleMode;
}
public void CopyTo(CanvasScaler dest)
{
dest.defaultSpriteDPI = m_DefaultSpriteDPI;
dest.dynamicPixelsPerUnit = m_DynamicPixelsPerUnit;
dest.fallbackScreenDPI = m_FallbackScreenDPI;
dest.matchWidthOrHeight = m_MatchWidthOrHeight;
dest.physicalUnit = m_PhysicalUnit;
dest.referencePixelsPerUnit = m_ReferencePixelsPerUnit;
dest.referenceResolution = m_ReferenceResolution;
dest.scaleFactor = m_ScaleFactor;
dest.screenMatchMode = m_ScreenMatchMode;
dest.uiScaleMode = m_UiScaleMode;
}
}
class GraphicRaycasterSettings
{
public bool present { get; set; }
LayerMask m_BlockingMask;
GraphicRaycaster.BlockingObjects m_BlockingObjects;
bool m_IgnoreReversedGraphics;
public void CopyFrom(GraphicRaycaster source)
{
m_BlockingMask = source.blockingMask;
m_BlockingObjects = source.blockingObjects;
m_IgnoreReversedGraphics = source.ignoreReversedGraphics;
}
public void CopyTo(GraphicRaycaster dest)
{
dest.blockingMask = m_BlockingMask;
dest.blockingObjects = m_BlockingObjects;
dest.ignoreReversedGraphics = m_IgnoreReversedGraphics;
}
}
CanvasTracker m_Tracker;
readonly CanvasSettings m_CanvasSettings = new CanvasSettings();
readonly CanvasScalerSettings m_CanvasScalerSettings = new CanvasScalerSettings();
readonly GraphicRaycasterSettings m_GraphicRaycasterSettings = new GraphicRaycasterSettings();
bool m_WasNested;
bool m_Nested;
bool m_RaysDisabled;
Canvas m_Canvas;
GraphicRaycaster m_Raycaster;
TrackedDeviceGraphicRaycaster m_TrackedDeviceGraphicRaycaster;
float m_CheckTimer;
internal void Initialize(CanvasTracker tracker)
{
m_Tracker = tracker;
var go = m_Tracker.gameObject;
go.TryGetComponent(out m_Canvas);
go.TryGetComponent(out m_Raycaster);
CheckForNestedChanges(true);
}
internal void CheckForNestedChanges(bool force = false)
{
if (!m_Tracker.transformDirty && !force)
return;
m_Tracker.transformDirty = false;
var transform = m_Tracker.transform;
// Check for nesting
var parent = transform.parent;
var parentCanvas = parent != null ? parent.GetComponentInParent<Canvas>() : null;
m_Nested = (parentCanvas != null);
// If nested has occurred, remove unnecessary components
if (m_Nested && (!m_WasNested || force))
{
if (transform.TryGetComponent<CanvasScaler>(out var canvasScaler))
{
m_CanvasScalerSettings.present = true;
m_CanvasScalerSettings.CopyFrom(canvasScaler);
Destroy(canvasScaler);
}
else
m_CanvasScalerSettings.present = false;
if (transform.TryGetComponent<GraphicRaycaster>(out var graphicRaycaster))
{
m_GraphicRaycasterSettings.present = true;
m_GraphicRaycasterSettings.CopyFrom(graphicRaycaster);
Destroy(graphicRaycaster);
}
else
m_GraphicRaycasterSettings.present = false;
if (transform.TryGetComponent<Canvas>(out var canvas))
{
m_CanvasSettings.present = true;
m_CanvasSettings.CopyFrom(canvas);
Destroy(canvas);
}
else
m_CanvasSettings.present = false;
if (transform.TryGetComponent(out m_TrackedDeviceGraphicRaycaster))
{
// ReSharper disable once PossibleNullReferenceException -- already verified above with m_Nested
if (!parentCanvas.TryGetComponent<TrackedDeviceGraphicRaycaster>(out _))
Debug.LogWarning($"Tracked device raycaster not present on parent canvas: {parent.name}. Tracked device input will likely not work on: {transform.name}", transform);
m_TrackedDeviceGraphicRaycaster.enabled = false;
}
}
// If nesting has not occurred, restore the components
if (!m_Nested && (m_WasNested || force))
{
if (m_CanvasSettings.present)
{
var go = transform.gameObject;
m_Canvas = go.AddComponent<Canvas>();
m_CanvasSettings.CopyTo(m_Canvas);
if (m_CanvasScalerSettings.present)
{
var canvasScaler = go.AddComponent<CanvasScaler>();
m_CanvasScalerSettings.CopyTo(canvasScaler);
}
if (m_GraphicRaycasterSettings.present)
{
m_Raycaster = go.AddComponent<GraphicRaycaster>();
m_GraphicRaycasterSettings.CopyTo(m_Raycaster);
}
if (m_TrackedDeviceGraphicRaycaster != null)
m_TrackedDeviceGraphicRaycaster.enabled = true;
}
}
m_WasNested = m_Nested;
}
internal void CheckForOutOfView(Transform gazeSource, float fovAngle, float facingAngle, float maxDistance)
{
if (m_Nested)
return;
if (m_Canvas.renderMode != RenderMode.WorldSpace)
return;
m_CheckTimer += Time.deltaTime;
if (m_CheckTimer < k_CanvasCheckInterval)
return;
m_CheckTimer = 0f;
var transform = m_Canvas.transform;
var gazePos = gazeSource.position;
var gazeDir = gazeSource.forward;
var targetPos = transform.position;
var targetDir = transform.forward;
// Check if canvas is facing away from camera
// Check if canvas is off camera
// If any of these are true, disable the ray casters
var disableRayCasters = BurstGazeUtility.IsOutsideGaze(gazePos, gazeDir, targetPos, fovAngle) ||
!BurstGazeUtility.IsAlignedToGazeForward(gazeDir, targetDir, facingAngle) &&
BurstGazeUtility.IsOutsideDistanceRange(gazePos, targetPos, maxDistance);
// See if state changed
if (m_RaysDisabled != disableRayCasters)
{
m_RaysDisabled = disableRayCasters;
// Disable tracked device caster
if (m_Raycaster != null)
m_Raycaster.enabled = !m_RaysDisabled;
if (m_TrackedDeviceGraphicRaycaster != null)
m_TrackedDeviceGraphicRaycaster.enabled = !m_RaysDisabled;
}
}
}
[SerializeField]
[Tooltip("How wide of an field-of-view to use when determining if a canvas is in view.")]
float m_RayPositionIgnoreAngle = 45f;
/// <summary>
/// How wide of an field-of-view to use when determining if a canvas is in view.
/// </summary>
public float rayPositionIgnoreAngle
{
get => m_RayPositionIgnoreAngle;
set => m_RayPositionIgnoreAngle = value;
}
[SerializeField]
[Tooltip("How much the camera and canvas rotate away from one another and still be considered facing.")]
float m_RayFacingIgnoreAngle = 75f;
/// <summary>
/// How much the camera and canvas rotate away from one another and still be considered facing.
/// </summary>
public float rayFacingIgnoreAngle
{
get => m_RayFacingIgnoreAngle;
set => m_RayFacingIgnoreAngle = value;
}
[SerializeField]
[Tooltip("How far away a canvas can be from this camera and still receive input.")]
float m_RayPositionIgnoreDistance = 25f;
/// <summary>
/// How far away a canvas can be from this camera and still receive input.
/// </summary>
public float rayPositionIgnoreDistance
{
get => m_RayPositionIgnoreDistance;
set => m_RayPositionIgnoreDistance = value;
}
Camera m_CullingCamera;
Transform m_CullingCameraTransform;
readonly Dictionary<CanvasTracker, CanvasState> m_CanvasTrackers = new Dictionary<CanvasTracker, CanvasState>();
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void Awake()
{
if (ComponentLocatorUtility<CanvasOptimizer>.FindComponent() != this)
{
Debug.LogWarning($"Duplicate Canvas Optimizer {gameObject.name} found. Only one Canvas Optimizer is allowed in the scene at a time.", this);
Destroy(this);
enabled = false;
return;
}
FindCullingCamera();
// Canvases cannot auto-register, so collect all canvases in the scene at start
#if UNITY_2023_1_OR_NEWER
var canvases = FindObjectsByType<Canvas>(FindObjectsInactive.Include, FindObjectsSortMode.None);
#else
var canvases = FindObjectsOfType<Canvas>(true);
#endif
for (var index = 0; index < canvases.Length; ++index)
{
var canvas = canvases[index];
RegisterCanvas(canvas);
}
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void Update()
{
CheckForNestedCanvasChanges();
CheckForOutOfViewCanvases();
}
/// <summary>
/// Allows the canvas optimizer to process this canvas. Will be called automatically for all canvases in the scene.
/// </summary>
/// <param name="canvas">The canvas to optimize.</param>
/// <remarks>This only needs to be called manually for canvases instantiated at runtime.</remarks>
public void RegisterCanvas(Canvas canvas)
{
var canvasTracker = InitializeCanvasTracking(canvas);
if (m_CanvasTrackers.ContainsKey(canvasTracker))
return;
var canvasState = new CanvasState();
canvasState.Initialize(canvasTracker);
m_CanvasTrackers.Add(canvasTracker, canvasState);
}
/// <summary>
/// Tells the canvas optimizer to stop processing this canvas. Will be called automatically for all canvases in the scene.
/// </summary>
/// <param name="canvas">The canvas to stop optimizing.</param>
/// <remarks>This only needs to be called manually for canvases destroyed during runtime.</remarks>
public void UnregisterCanvas(Canvas canvas)
{
// Remove matching canvas tracker
if (canvas.TryGetComponent(out CanvasTracker toRemove))
{
m_CanvasTrackers.Remove(toRemove);
}
}
static CanvasTracker InitializeCanvasTracking(Canvas target)
{
// Put parent tracker on target
if (!target.gameObject.TryGetComponent(out CanvasTracker tracker))
{
tracker = target.gameObject.AddComponent<CanvasTracker>();
tracker.hideFlags = HideFlags.HideAndDontSave;
}
return tracker;
}
void CheckForNestedCanvasChanges()
{
foreach (var canvasData in m_CanvasTrackers.Values)
{
canvasData.CheckForNestedChanges();
}
}
void CheckForOutOfViewCanvases()
{
// Find the new main camera if necessary
if (m_CullingCamera == null || !m_CullingCamera.enabled)
{
FindCullingCamera();
if (m_CullingCameraTransform == null)
return;
}
foreach (var canvasData in m_CanvasTrackers.Values)
{
canvasData.CheckForOutOfView(m_CullingCameraTransform, m_RayPositionIgnoreAngle, m_RayFacingIgnoreAngle, m_RayPositionIgnoreDistance);
}
}
void FindCullingCamera()
{
m_CullingCamera = Camera.main;
m_CullingCameraTransform = m_CullingCamera != null ? m_CullingCamera.transform : null;
}
}
}