summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/DrawingManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/DrawingManager.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/DrawingManager.cs788
1 files changed, 788 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/DrawingManager.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/DrawingManager.cs
new file mode 100644
index 0000000..3e40815
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/DrawingManager.cs
@@ -0,0 +1,788 @@
+#pragma warning disable 649 // Field `Drawing.GizmoContext.activeTransform' is never assigned to, and will always have its default value `null'. Not used outside of the unity editor.
+using UnityEngine;
+using System.Collections;
+using System;
+#if UNITY_EDITOR
+using UnityEditor;
+using UnityEditor.SceneManagement;
+#endif
+using System.Collections.Generic;
+using Unity.Jobs;
+using Unity.Mathematics;
+using UnityEngine.Rendering;
+using Unity.Profiling;
+#if MODULE_RENDER_PIPELINES_UNIVERSAL
+using UnityEngine.Rendering.Universal;
+#endif
+#if MODULE_RENDER_PIPELINES_HIGH_DEFINITION
+using UnityEngine.Rendering.HighDefinition;
+#endif
+
+namespace Pathfinding.Drawing {
+ /// <summary>Info about the current selection in the editor</summary>
+ public static class GizmoContext {
+#if UNITY_EDITOR
+ static Transform activeTransform;
+#endif
+
+ static HashSet<Transform> selectedTransforms = new HashSet<Transform>();
+
+ static internal bool drawingGizmos;
+ static internal bool dirty;
+ private static int selectionSizeInternal;
+
+ /// <summary>Number of top-level transforms that are selected</summary>
+ public static int selectionSize {
+ get {
+ Refresh();
+ return selectionSizeInternal;
+ }
+ private set {
+ selectionSizeInternal = value;
+ }
+ }
+
+ internal static void SetDirty () {
+ dirty = true;
+ }
+
+ private static void Refresh () {
+#if UNITY_EDITOR
+ if (!drawingGizmos) throw new System.Exception("Can only be used inside the ALINE library's gizmo drawing functions.");
+ if (dirty) {
+ dirty = false;
+ DrawingManager.MarkerRefreshSelectionCache.Begin();
+ activeTransform = Selection.activeTransform;
+ selectedTransforms.Clear();
+ var topLevel = Selection.transforms;
+ for (int i = 0; i < topLevel.Length; i++) selectedTransforms.Add(topLevel[i]);
+ selectionSize = topLevel.Length;
+ DrawingManager.MarkerRefreshSelectionCache.End();
+ }
+#endif
+ }
+
+ /// <summary>
+ /// True if the component is selected.
+ /// This is a deep selection: even children of selected transforms are considered to be selected.
+ /// </summary>
+ public static bool InSelection (Component c) {
+ return InSelection(c.transform);
+ }
+
+ /// <summary>
+ /// True if the transform is selected.
+ /// This is a deep selection: even children of selected transforms are considered to be selected.
+ /// </summary>
+ public static bool InSelection (Transform tr) {
+ Refresh();
+ var leaf = tr;
+ while (tr != null) {
+ if (selectedTransforms.Contains(tr)) {
+ selectedTransforms.Add(leaf);
+ return true;
+ }
+ tr = tr.parent;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// True if the component is shown in the inspector.
+ /// The active selection is the GameObject that is currently visible in the inspector.
+ /// </summary>
+ public static bool InActiveSelection (Component c) {
+ return InActiveSelection(c.transform);
+ }
+
+ /// <summary>
+ /// True if the transform is shown in the inspector.
+ /// The active selection is the GameObject that is currently visible in the inspector.
+ /// </summary>
+ public static bool InActiveSelection (Transform tr) {
+#if UNITY_EDITOR
+ Refresh();
+ return tr.transform == activeTransform;
+#else
+ return false;
+#endif
+ }
+ }
+
+ /// <summary>
+ /// Every object that wants to draw gizmos should implement this interface.
+ /// See: <see cref="Drawing.MonoBehaviourGizmos"/>
+ /// </summary>
+ public interface IDrawGizmos {
+ void DrawGizmos();
+ }
+
+ public enum DetectedRenderPipeline {
+ BuiltInOrCustom,
+ HDRP,
+ URP
+ }
+
+ /// <summary>
+ /// Global script which draws debug items and gizmos.
+ /// If a Draw.* method has been used or if any script inheriting from the <see cref="Drawing.MonoBehaviourGizmos"/> class is in the scene then an instance of this script
+ /// will be created and put on a hidden GameObject.
+ ///
+ /// It will inject drawing logic into any cameras that are rendered.
+ ///
+ /// Usually you never have to interact with this class.
+ /// </summary>
+ [ExecuteAlways]
+ [AddComponentMenu("")]
+ public class DrawingManager : MonoBehaviour {
+ public DrawingData gizmos;
+ static List<IDrawGizmos> gizmoDrawers = new List<IDrawGizmos>();
+ static Dictionary<System.Type, bool> gizmoDrawerTypes = new Dictionary<System.Type, bool>();
+ static DrawingManager _instance;
+ bool framePassed;
+ int lastFrameCount = int.MinValue;
+ float lastFrameTime = -float.NegativeInfinity;
+ int lastFilterFrame;
+#if UNITY_EDITOR
+ bool builtGizmos;
+#endif
+
+ /// <summary>True if OnEnable has been called on this instance and OnDisable has not</summary>
+ [SerializeField]
+ bool actuallyEnabled;
+
+ RedrawScope previousFrameRedrawScope;
+
+ /// <summary>
+ /// Allow rendering to cameras that render to RenderTextures.
+ /// By default cameras which render to render textures are never rendered to.
+ /// You may enable this if you wish.
+ ///
+ /// See: <see cref="Drawing.CommandBuilder.cameraTargets"/>
+ /// See: advanced (view in online documentation for working links)
+ /// </summary>
+ public static bool allowRenderToRenderTextures = false;
+ public static bool drawToAllCameras = false;
+
+ /// <summary>
+ /// Multiply all line widths by this value.
+ /// This can be used to make lines thicker or thinner.
+ ///
+ /// This is primarily useful when generating screenshots, and you want to render at a higher resolution before scaling down the image.
+ ///
+ /// It is only read when a camera is being rendered. So it cannot be used to change line thickness on a per-item basis.
+ /// Use <see cref="Draw.WithLineWidth"/> for that.
+ /// </summary>
+ public static float lineWidthMultiplier = 1.0f;
+
+ CommandBuffer commandBuffer;
+
+ [System.NonSerialized]
+ DetectedRenderPipeline detectedRenderPipeline = DetectedRenderPipeline.BuiltInOrCustom;
+
+#if MODULE_RENDER_PIPELINES_UNIVERSAL
+ HashSet<ScriptableRenderer> scriptableRenderersWithPass = new HashSet<ScriptableRenderer>();
+ AlineURPRenderPassFeature renderPassFeature;
+#endif
+
+ private static readonly ProfilerMarker MarkerALINE = new ProfilerMarker("ALINE");
+ private static readonly ProfilerMarker MarkerCommandBuffer = new ProfilerMarker("Executing command buffer");
+ private static readonly ProfilerMarker MarkerFrameTick = new ProfilerMarker("Frame Tick");
+ private static readonly ProfilerMarker MarkerFilterDestroyedObjects = new ProfilerMarker("Filter destroyed objects");
+ internal static readonly ProfilerMarker MarkerRefreshSelectionCache = new ProfilerMarker("Refresh Selection Cache");
+ private static readonly ProfilerMarker MarkerGizmosAllowed = new ProfilerMarker("GizmosAllowed");
+ private static readonly ProfilerMarker MarkerDrawGizmos = new ProfilerMarker("DrawGizmos");
+ private static readonly ProfilerMarker MarkerSubmitGizmos = new ProfilerMarker("Submit Gizmos");
+
+ public static DrawingManager instance {
+ get {
+ if (_instance == null) Init();
+ return _instance;
+ }
+ }
+
+#if UNITY_EDITOR
+ [InitializeOnLoadMethod]
+#endif
+ public static void Init () {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (Unity.Jobs.LowLevel.Unsafe.JobsUtility.IsExecutingJob) throw new System.Exception("Draw.* methods cannot be called from inside a job. See the documentation for info about how to use drawing functions from the Unity Job System.");
+#endif
+ if (_instance != null) return;
+
+ // Here one might try to look for existing instances of the class that haven't yet been enabled.
+ // However, this turns out to be tricky.
+ // Resources.FindObjectsOfTypeAll<T>() is the only call that includes HideInInspector GameObjects.
+ // But it is hard to distinguish between objects that are internal ones which will never be enabled and objects that will be enabled.
+ // Checking .gameObject.scene.isLoaded doesn't work reliably (object may be enabled and working even if isLoaded is false)
+ // Checking .gameObject.scene.isValid doesn't work reliably (object may be enabled and working even if isValid is false)
+
+ // So instead we just always create a new instance. This is not a particularly heavy operation and it only happens once per game, so why not.
+ // The OnEnable call will clean up duplicate managers if there are any.
+
+ var go = new GameObject("RetainedGizmos") {
+ hideFlags = HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInInspector | HideFlags.HideInHierarchy
+ };
+ _instance = go.AddComponent<DrawingManager>();
+ if (Application.isPlaying) DontDestroyOnLoad(go);
+ }
+
+ /// <summary>Detects which render pipeline is being used and configures them for rendering</summary>
+ void RefreshRenderPipelineMode () {
+ var pipelineType = RenderPipelineManager.currentPipeline != null? RenderPipelineManager.currentPipeline.GetType() : null;
+
+#if MODULE_RENDER_PIPELINES_HIGH_DEFINITION
+ if (pipelineType == typeof(HDRenderPipeline)) {
+ if (detectedRenderPipeline != DetectedRenderPipeline.HDRP) {
+ detectedRenderPipeline = DetectedRenderPipeline.HDRP;
+ if (!_instance.gameObject.TryGetComponent<CustomPassVolume>(out CustomPassVolume volume)) {
+ volume = _instance.gameObject.AddComponent<CustomPassVolume>();
+ volume.isGlobal = true;
+ volume.injectionPoint = CustomPassInjectionPoint.AfterPostProcess;
+ volume.customPasses.Add(new AlineHDRPCustomPass());
+ }
+
+ var asset = GraphicsSettings.defaultRenderPipeline as HDRenderPipelineAsset;
+ if (asset != null) {
+ if (!asset.currentPlatformRenderPipelineSettings.supportCustomPass) {
+ Debug.LogWarning("A*: The current render pipeline has custom pass support disabled. The A* Pathfinding Project will not be able to render anything. Please enable custom pass support on your HDRenderPipelineAsset.", asset);
+ }
+ }
+ }
+ return;
+ }
+#endif
+#if MODULE_RENDER_PIPELINES_UNIVERSAL
+ if (pipelineType == typeof(UniversalRenderPipeline)) {
+ detectedRenderPipeline = DetectedRenderPipeline.URP;
+ return;
+ }
+#endif
+ detectedRenderPipeline = DetectedRenderPipeline.BuiltInOrCustom;
+ }
+
+#if UNITY_EDITOR
+ void DelayedDestroy () {
+ EditorApplication.update -= DelayedDestroy;
+ // Check if the object still exists (it might have been destroyed in some other way already).
+ if (gameObject) GameObject.DestroyImmediate(gameObject);
+ }
+
+ void OnPlayModeStateChanged (PlayModeStateChange change) {
+ if (change == PlayModeStateChange.ExitingEditMode || change == PlayModeStateChange.ExitingPlayMode) {
+ gizmos.OnChangingPlayMode();
+ }
+ }
+#endif
+
+ void OnEnable () {
+ if (_instance == null) _instance = this;
+
+ // Ensure we don't have duplicate managers
+ if (_instance != this) {
+ // We cannot destroy the object while it is being enabled, so we need to delay it a bit
+#if UNITY_EDITOR
+ // This is only important in the editor to avoid a build-up of old managers.
+ // In an actual game at most 1 (though in practice zero) old managers will be laying around.
+ // It would be nice to use a coroutine for this instead, but unfortunately they do not work for objects marked with HideAndDontSave.
+ EditorApplication.update += DelayedDestroy;
+#endif
+ return;
+ }
+
+ actuallyEnabled = true;
+ if (gizmos == null) gizmos = new DrawingData();
+ gizmos.frameRedrawScope = new RedrawScope(gizmos);
+ Draw.builder = gizmos.GetBuiltInBuilder(false);
+ Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
+ commandBuffer = new CommandBuffer();
+ commandBuffer.name = "ALINE Gizmos";
+
+ // Callback when rendering with the built-in render pipeline
+ Camera.onPostRender += PostRender;
+ // Callback when rendering with a scriptable render pipeline
+#if UNITY_2023_3_OR_NEWER
+ UnityEngine.Rendering.RenderPipelineManager.beginContextRendering += BeginContextRendering;
+#else
+ UnityEngine.Rendering.RenderPipelineManager.beginFrameRendering += BeginFrameRendering;
+#endif
+ UnityEngine.Rendering.RenderPipelineManager.beginCameraRendering += BeginCameraRendering;
+ UnityEngine.Rendering.RenderPipelineManager.endCameraRendering += EndCameraRendering;
+#if UNITY_EDITOR
+ EditorApplication.update += OnEditorUpdate;
+ EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
+#endif
+ }
+
+ void BeginContextRendering (ScriptableRenderContext context, List<Camera> cameras) {
+ RefreshRenderPipelineMode();
+ }
+
+ void BeginFrameRendering (ScriptableRenderContext context, Camera[] cameras) {
+ RefreshRenderPipelineMode();
+ }
+
+ void BeginCameraRendering (ScriptableRenderContext context, Camera camera) {
+#if MODULE_RENDER_PIPELINES_UNIVERSAL
+ if (detectedRenderPipeline == DetectedRenderPipeline.URP) {
+ var data = camera.GetUniversalAdditionalCameraData();
+ if (data != null) {
+ var renderer = data.scriptableRenderer;
+ if (renderPassFeature == null) {
+ renderPassFeature = ScriptableObject.CreateInstance<AlineURPRenderPassFeature>();
+ }
+ renderPassFeature.AddRenderPasses(renderer);
+ }
+ }
+#endif
+ }
+
+ void OnDisable () {
+ if (!actuallyEnabled) return;
+ actuallyEnabled = false;
+ commandBuffer.Dispose();
+ commandBuffer = null;
+ Camera.onPostRender -= PostRender;
+#if UNITY_2023_3_OR_NEWER
+ UnityEngine.Rendering.RenderPipelineManager.beginContextRendering -= BeginContextRendering;
+#else
+ UnityEngine.Rendering.RenderPipelineManager.beginFrameRendering -= BeginFrameRendering;
+#endif
+ UnityEngine.Rendering.RenderPipelineManager.beginCameraRendering -= BeginCameraRendering;
+ UnityEngine.Rendering.RenderPipelineManager.endCameraRendering -= EndCameraRendering;
+#if UNITY_EDITOR
+ EditorApplication.update -= OnEditorUpdate;
+ EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
+#endif
+ // Gizmos can be null here if this GameObject was duplicated by a user in the hierarchy.
+ if (gizmos != null) {
+ Draw.builder.DiscardAndDisposeInternal();
+ Draw.ingame_builder.DiscardAndDisposeInternal();
+ gizmos.ClearData();
+ }
+#if MODULE_RENDER_PIPELINES_UNIVERSAL
+ if (renderPassFeature != null) {
+ ScriptableObject.DestroyImmediate(renderPassFeature);
+ renderPassFeature = null;
+ }
+#endif
+ }
+
+ // When enter play mode = reload scene & reload domain
+ // editor => play mode: OnDisable -> OnEnable (same object)
+ // play mode => editor: OnApplicationQuit (note: no OnDisable/OnEnable)
+ // When enter play mode = reload scene & !reload domain
+ // editor => play mode: Nothing
+ // play mode => editor: OnApplicationQuit
+ // When enter play mode = !reload scene & !reload domain
+ // editor => play mode: Nothing
+ // play mode => editor: OnApplicationQuit
+ // OnDestroy is never really called for this object (unless Unity or the game quits I quess)
+
+ // TODO: Should run in OnDestroy. OnApplicationQuit runs BEFORE OnDestroy (which we do not want)
+ // private void OnApplicationQuit () {
+ // Debug.Log("OnApplicationQuit");
+ // Draw.builder.DiscardAndDisposeInternal();
+ // Draw.ingame_builder.DiscardAndDisposeInternal();
+ // gizmos.ClearData();
+ // Draw.builder = gizmos.GetBuiltInBuilder(false);
+ // Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
+ // }
+
+ const float NO_DRAWING_TIMEOUT_SECS = 10;
+
+ void OnEditorUpdate () {
+ framePassed = true;
+ CleanupIfNoCameraRendered();
+ }
+
+ void Update () {
+ if (actuallyEnabled) CleanupIfNoCameraRendered();
+ }
+
+ void CleanupIfNoCameraRendered () {
+ if (Time.frameCount > lastFrameCount + 1) {
+ // More than one frame old
+ // It is possible no camera is being rendered at all.
+ // Ensure we don't get any memory leaks from drawing items being queued every frame.
+ CheckFrameTicking();
+ gizmos.PostRenderCleanup();
+
+ // Note: We do not always want to call the above method here
+ // because it is nicer to call it right after the cameras have been rendered.
+ // Otherwise drawing items queued before Update/OnEditorUpdate or after Update/OnEditorUpdate may end up
+ // in different frames (for the purposes of rendering gizmos)
+ }
+
+ if (Time.realtimeSinceStartup - lastFrameTime > NO_DRAWING_TIMEOUT_SECS) {
+ // More than NO_DRAWING_TIMEOUT_SECS seconds since we drew the last frame.
+ // In the editor some script could be queuing drawing commands in e.g. EditorWindow.Update without the scene
+ // view or any game view being re-rendered. We discard these commands if nothing has been rendered for a long time.
+ Draw.builder.DiscardAndDisposeInternal();
+ Draw.ingame_builder.DiscardAndDisposeInternal();
+ Draw.builder = gizmos.GetBuiltInBuilder(false);
+ Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
+ lastFrameTime = Time.realtimeSinceStartup;
+ RemoveDestroyedGizmoDrawers();
+ }
+
+ // Avoid potential memory leak if gizmos are not being drawn
+ if (lastFilterFrame - Time.frameCount > 5) {
+ lastFilterFrame = Time.frameCount;
+ RemoveDestroyedGizmoDrawers();
+ }
+ }
+
+ internal void ExecuteCustomRenderPass (ScriptableRenderContext context, Camera camera) {
+ MarkerALINE.Begin();
+ commandBuffer.Clear();
+ SubmitFrame(camera, new DrawingData.CommandBufferWrapper { cmd = commandBuffer }, true);
+ context.ExecuteCommandBuffer(commandBuffer);
+ MarkerALINE.End();
+ }
+
+#if MODULE_RENDER_PIPELINES_UNIVERSAL
+ internal void ExecuteCustomRenderGraphPass (DrawingData.CommandBufferWrapper cmd, Camera camera) {
+ MarkerALINE.Begin();
+ SubmitFrame(camera, cmd, true);
+ MarkerALINE.End();
+ }
+#endif
+
+ private void EndCameraRendering (ScriptableRenderContext context, Camera camera) {
+ if (detectedRenderPipeline == DetectedRenderPipeline.BuiltInOrCustom) {
+ // Execute the custom render pass after the camera has finished rendering.
+ // For the HDRP and URP the render pass will already have been executed.
+ // However for a custom render pipline we execute the rendering code here.
+ // This is only best effort. It's impossible to be compatible with all custom render pipelines.
+ // However it should work for most simple ones.
+ // For Unity's built-in render pipeline the EndCameraRendering method will never be called.
+ ExecuteCustomRenderPass(context, camera);
+ }
+ }
+
+ void PostRender (Camera camera) {
+ // This method is only called when using Unity's built-in render pipeline
+ commandBuffer.Clear();
+ SubmitFrame(camera, new DrawingData.CommandBufferWrapper { cmd = commandBuffer }, false);
+ MarkerCommandBuffer.Begin();
+ Graphics.ExecuteCommandBuffer(commandBuffer);
+ MarkerCommandBuffer.End();
+ }
+
+ void CheckFrameTicking () {
+ MarkerFrameTick.Begin();
+ if (Time.frameCount != lastFrameCount) {
+ framePassed = true;
+ lastFrameCount = Time.frameCount;
+ lastFrameTime = Time.realtimeSinceStartup;
+ previousFrameRedrawScope = gizmos.frameRedrawScope;
+ gizmos.frameRedrawScope = new RedrawScope(gizmos);
+ Draw.builder.DisposeInternal();
+ Draw.ingame_builder.DisposeInternal();
+ Draw.builder = gizmos.GetBuiltInBuilder(false);
+ Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
+ } else if (framePassed && Application.isPlaying) {
+ // Rendered frame passed without a game frame passing!
+ // This might mean the game is paused.
+ // Redraw gizmos while the game is paused.
+ // It might also just mean that we are rendering with multiple cameras.
+ previousFrameRedrawScope.Draw();
+ }
+
+ if (framePassed) {
+ gizmos.TickFramePreRender();
+#if UNITY_EDITOR
+ builtGizmos = false;
+#endif
+ framePassed = false;
+ }
+ MarkerFrameTick.End();
+ }
+
+ internal void SubmitFrame (Camera camera, DrawingData.CommandBufferWrapper cmd, bool usingRenderPipeline) {
+#if UNITY_EDITOR
+ bool isSceneViewCamera = SceneView.currentDrawingSceneView != null && SceneView.currentDrawingSceneView.camera == camera;
+#else
+ bool isSceneViewCamera = false;
+#endif
+ // Do not include when rendering to a texture unless this is a scene view camera
+ bool allowCameraDefault = allowRenderToRenderTextures || drawToAllCameras || camera.targetTexture == null || isSceneViewCamera;
+
+ CheckFrameTicking();
+
+ Submit(camera, cmd, usingRenderPipeline, allowCameraDefault);
+
+ gizmos.PostRenderCleanup();
+ }
+
+#if UNITY_EDITOR
+ static readonly System.Reflection.MethodInfo IsGizmosAllowedForObject = typeof(UnityEditor.EditorGUIUtility).GetMethod("IsGizmosAllowedForObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
+ readonly System.Object[] cachedObjectParameterArray = new System.Object[1];
+#endif
+
+ readonly Dictionary<System.Type, bool> typeToGizmosEnabled = new Dictionary<Type, bool>();
+
+ bool ShouldDrawGizmos (UnityEngine.Object obj) {
+#if UNITY_EDITOR
+ // Use reflection to call EditorGUIUtility.IsGizmosAllowedForObject which is an internal method.
+ // It is exactly the information we want though.
+ // In case Unity has changed its API or something so that the method can no longer be found then just return true
+ cachedObjectParameterArray[0] = obj;
+ return IsGizmosAllowedForObject == null || (bool)IsGizmosAllowedForObject.Invoke(null, cachedObjectParameterArray);
+#else
+ return true;
+#endif
+ }
+
+ static void RemoveDestroyedGizmoDrawers () {
+ MarkerFilterDestroyedObjects.Begin();
+ int j = 0;
+ for (int i = 0; i < gizmoDrawers.Count; i++) {
+ var v = gizmoDrawers[i];
+ if (v as MonoBehaviour) {
+ gizmoDrawers[j] = v;
+ j++;
+ }
+ }
+ gizmoDrawers.RemoveRange(j, gizmoDrawers.Count - j);
+ MarkerFilterDestroyedObjects.End();
+ }
+
+#if UNITY_EDITOR
+ void DrawGizmos (bool usingRenderPipeline) {
+ GizmoContext.SetDirty();
+ MarkerGizmosAllowed.Begin();
+ typeToGizmosEnabled.Clear();
+
+ // Fill the typeToGizmosEnabled dict with info about which classes should be drawn
+#if UNITY_2022_1_OR_NEWER
+ // In Unity 2022.1 we can use a new utility class which is more robust.
+ foreach (var tp in gizmoDrawerTypes) {
+ if (GizmoUtility.TryGetGizmoInfo(tp.Key, out var gizmoInfo)) {
+ typeToGizmosEnabled[tp.Key] = gizmoInfo.gizmoEnabled;
+ } else {
+ typeToGizmosEnabled[tp.Key] = true;
+ }
+ }
+#else
+ // We take advantage of the fact that IsGizmosAllowedForObject only depends on the type of the object and if it is active and enabled
+ // and not the specific object instance.
+ // When using a render pipeline the ShouldDrawGizmos method cannot be used because it seems to occasionally crash Unity :(
+ // So we need these two separate cases.
+ if (!usingRenderPipeline) {
+ for (int i = gizmoDrawers.Count - 1; i >= 0; i--) {
+ var tp = gizmoDrawers[i].GetType();
+ if (!typeToGizmosEnabled.ContainsKey(tp) && (gizmoDrawers[i] as MonoBehaviour).isActiveAndEnabled) {
+ typeToGizmosEnabled[tp] = ShouldDrawGizmos((UnityEngine.Object)gizmoDrawers[i]);
+ }
+ }
+ foreach (var tp in gizmoDrawerTypes) {
+ // Check if there were no enabled objects of that type at all
+ if (!typeToGizmosEnabled.ContainsKey(tp.Key)) typeToGizmosEnabled[tp.Key] = false;
+ }
+ } else {
+ foreach (var tp in gizmoDrawerTypes) {
+ typeToGizmosEnabled[tp.Key] = true;
+ }
+ }
+#endif
+
+ MarkerGizmosAllowed.End();
+
+ // Set the current frame's redraw scope to an empty scope.
+ // This is because gizmos are rendered every frame anyway so we never want to redraw them.
+ // The frame redraw scope is otherwise used when the game has been paused.
+ var frameRedrawScope = gizmos.frameRedrawScope;
+ gizmos.frameRedrawScope = default(RedrawScope);
+
+#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
+ var currentStage = StageUtility.GetCurrentStage();
+ var isInNonMainStage = currentStage != StageUtility.GetMainStage();
+#endif
+
+ // This would look nicer as a 'using' block, but built-in command builders
+ // cannot be disposed normally to prevent user error.
+ // The try-finally is equivalent to a 'using' block.
+ var gizmoBuilder = gizmos.GetBuiltInBuilder();
+ // Replace Draw.builder with a custom one just for gizmos
+ var debugBuilder = Draw.builder;
+ MarkerDrawGizmos.Begin();
+ GizmoContext.drawingGizmos = true;
+ try {
+ Draw.builder = gizmoBuilder;
+ if (usingRenderPipeline) {
+ for (int i = gizmoDrawers.Count - 1; i >= 0; i--) {
+ var mono = gizmoDrawers[i] as MonoBehaviour;
+#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
+ // True if the scene is in isolation mode (e.g. focusing on a single prefab) and this object is not part of that sub-stage
+ var disabledDueToIsolationMode = isInNonMainStage && StageUtility.GetStage(mono.gameObject) != currentStage;
+#else
+ var disabledDueToIsolationMode = false;
+#endif
+#if UNITY_2022_1_OR_NEWER
+ var gizmosEnabled = mono.isActiveAndEnabled && typeToGizmosEnabled[gizmoDrawers[i].GetType()];
+#else
+ var gizmosEnabled = mono.isActiveAndEnabled;
+#endif
+ if (gizmosEnabled && (mono.hideFlags & HideFlags.HideInHierarchy) == 0 && !disabledDueToIsolationMode) {
+ try {
+ gizmoDrawers[i].DrawGizmos();
+ } catch (System.Exception e) {
+ Debug.LogException(e, mono);
+ }
+ }
+ }
+ } else {
+ for (int i = gizmoDrawers.Count - 1; i >= 0; i--) {
+ var mono = gizmoDrawers[i] as MonoBehaviour;
+ if (mono.isActiveAndEnabled && (mono.hideFlags & HideFlags.HideInHierarchy) == 0 && typeToGizmosEnabled[gizmoDrawers[i].GetType()]) {
+#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
+ // True if the scene is in isolation mode (e.g. focusing on a single prefab) and this object is not part of that sub-stage
+ var disabledDueToIsolationMode = isInNonMainStage && StageUtility.GetStage(mono.gameObject) != currentStage;
+#else
+ var disabledDueToIsolationMode = false;
+#endif
+ try {
+ if (!disabledDueToIsolationMode) gizmoDrawers[i].DrawGizmos();
+ } catch (System.Exception e) {
+ Debug.LogException(e, mono);
+ }
+ }
+ }
+ }
+ } finally {
+ GizmoContext.drawingGizmos = false;
+ MarkerDrawGizmos.End();
+ // Revert to the original builder
+ Draw.builder = debugBuilder;
+ gizmoBuilder.DisposeInternal();
+ }
+
+ gizmos.frameRedrawScope = frameRedrawScope;
+
+ // Schedule jobs that may have been scheduled while drawing gizmos
+ JobHandle.ScheduleBatchedJobs();
+ }
+#endif
+
+ /// <summary>Submit a camera for rendering.</summary>
+ /// <param name="allowCameraDefault">Indicates if built-in command builders and custom ones without a custom CommandBuilder.cameraTargets should render to this camera.</param>
+ void Submit (Camera camera, DrawingData.CommandBufferWrapper cmd, bool usingRenderPipeline, bool allowCameraDefault) {
+#if UNITY_EDITOR
+ bool drawGizmos = Handles.ShouldRenderGizmos() || drawToAllCameras;
+ // Only build gizmos if a camera actually needs them.
+ // This is only done for the first camera that needs them each frame.
+ if (drawGizmos && !builtGizmos && allowCameraDefault) {
+ RemoveDestroyedGizmoDrawers();
+ lastFilterFrame = Time.frameCount;
+ builtGizmos = true;
+ DrawGizmos(usingRenderPipeline);
+ }
+#else
+ bool drawGizmos = false;
+#endif
+
+ MarkerSubmitGizmos.Begin();
+ Draw.builder.DisposeInternal();
+ Draw.ingame_builder.DisposeInternal();
+ gizmos.Render(camera, drawGizmos, cmd, allowCameraDefault);
+ Draw.builder = gizmos.GetBuiltInBuilder(false);
+ Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
+ MarkerSubmitGizmos.End();
+ }
+
+ /// <summary>
+ /// Registers an object for gizmo drawing.
+ /// The DrawGizmos method on the object will be called every frame until it is destroyed (assuming there are cameras with gizmos enabled).
+ /// </summary>
+ public static void Register (IDrawGizmos item) {
+ var tp = item.GetType();
+
+ // Use reflection to figure out if the DrawGizmos method has not been overriden from the MonoBehaviourGizmos class.
+ // If it hasn't, then we know that this type will never draw gizmos and we can skip it.
+ // This improves performance by not having to keep track of objects and check if they are active and enabled every frame.
+ bool mayDrawGizmos;
+ if (gizmoDrawerTypes.TryGetValue(tp, out mayDrawGizmos)) {
+ } else {
+ var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic;
+ // Check for a public method first, and then an explicit interface implementation.
+ var m = tp.GetMethod("DrawGizmos", flags) ?? tp.GetMethod("Pathfinding.Drawing.IDrawGizmos.DrawGizmos", flags) ?? tp.GetMethod("Drawing.IDrawGizmos.DrawGizmos", flags);
+ if (m == null) {
+ throw new System.Exception("Could not find the DrawGizmos method in type " + tp.Name);
+ }
+ mayDrawGizmos = m.DeclaringType != typeof(MonoBehaviourGizmos);
+ gizmoDrawerTypes[tp] = mayDrawGizmos;
+ }
+ if (!mayDrawGizmos) return;
+
+ gizmoDrawers.Add(item);
+ }
+
+ /// <summary>
+ /// Get an empty builder for queuing drawing commands.
+ ///
+ /// <code>
+ /// // Create a new CommandBuilder
+ /// using (var draw = DrawingManager.GetBuilder()) {
+ /// // Use the exact same API as the global Draw class
+ /// draw.WireBox(Vector3.zero, Vector3.one);
+ /// }
+ /// </code>
+ /// See: <see cref="Drawing.CommandBuilder"/>
+ /// </summary>
+ /// <param name="renderInGame">If true, this builder will be rendered in standalone games and in the editor even if gizmos are disabled.
+ /// If false, it will only be rendered in the editor when gizmos are enabled.</param>
+ public static CommandBuilder GetBuilder(bool renderInGame = false) => instance.gizmos.GetBuilder(renderInGame);
+
+ /// <summary>
+ /// Get an empty builder for queuing drawing commands.
+ ///
+ /// See: <see cref="Drawing.CommandBuilder"/>
+ /// </summary>
+ /// <param name="redrawScope">Scope for this command builder. See #GetRedrawScope.</param>
+ /// <param name="renderInGame">If true, this builder will be rendered in standalone games and in the editor even if gizmos are disabled.
+ /// If false, it will only be rendered in the editor when gizmos are enabled.</param>
+ public static CommandBuilder GetBuilder(RedrawScope redrawScope, bool renderInGame = false) => instance.gizmos.GetBuilder(redrawScope, renderInGame);
+
+ /// <summary>
+ /// Get an empty builder for queuing drawing commands.
+ /// TODO: Example usage.
+ ///
+ /// See: <see cref="Drawing.CommandBuilder"/>
+ /// </summary>
+ /// <param name="hasher">Hash of whatever inputs you used to generate the drawing data.</param>
+ /// <param name="redrawScope">Scope for this command builder. See #GetRedrawScope.</param>
+ /// <param name="renderInGame">If true, this builder will be rendered in standalone games and in the editor even if gizmos are disabled.</param>
+ public static CommandBuilder GetBuilder(DrawingData.Hasher hasher, RedrawScope redrawScope = default, bool renderInGame = false) => instance.gizmos.GetBuilder(hasher, redrawScope, renderInGame);
+
+ /// <summary>
+ /// A scope which can be used to draw things over multiple frames.
+ ///
+ /// You can use <see cref="GetBuilder(RedrawScope,bool)"/> to get a builder with a given redraw scope.
+ /// Everything drawn using the redraw scope will be drawn every frame until the redraw scope is disposed.
+ ///
+ /// <code>
+ /// private RedrawScope redrawScope;
+ ///
+ /// void Start () {
+ /// redrawScope = DrawingManager.GetRedrawScope();
+ /// using (var builder = DrawingManager.GetBuilder(redrawScope)) {
+ /// builder.WireSphere(Vector3.zero, 1.0f, Color.red);
+ /// }
+ /// }
+ ///
+ /// void OnDestroy () {
+ /// redrawScope.Dispose();
+ /// }
+ /// </code>
+ /// </summary>
+ /// <param name="associatedGameObject">If not null, the scope will only be drawn if gizmos for the associated GameObject are drawn.
+ /// This is useful in the unity editor when e.g. opening a prefab in isolation mode, to disable redraw scopes for objects outside the prefab. Has no effect in standalone builds.</param>
+ public static RedrawScope GetRedrawScope (GameObject associatedGameObject = null) {
+ var scope = new RedrawScope(instance.gizmos);
+ scope.DrawUntilDispose(associatedGameObject);
+ return scope;
+ }
+ }
+}