#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; using UnityEngine.Profiling; #endif #if MODULE_RENDER_PIPELINES_HIGH_DEFINITION using UnityEngine.Rendering.HighDefinition; #endif namespace Pathfinding.Drawing { /// Info about the current selection in the editor public static class GizmoContext { #if UNITY_EDITOR static Transform activeTransform; #endif static HashSet selectedTransforms = new HashSet(); static internal bool drawingGizmos; static internal bool dirty; private static int selectionSizeInternal; /// Number of top-level transforms that are selected 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 } /// /// True if the component is selected. /// This is a deep selection: even children of selected transforms are considered to be selected. /// public static bool InSelection (Component c) { return InSelection(c.transform); } /// /// True if the transform is selected. /// This is a deep selection: even children of selected transforms are considered to be selected. /// 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; } /// /// True if the component is shown in the inspector. /// The active selection is the GameObject that is currently visible in the inspector. /// public static bool InActiveSelection (Component c) { return InActiveSelection(c.transform); } /// /// True if the transform is shown in the inspector. /// The active selection is the GameObject that is currently visible in the inspector. /// public static bool InActiveSelection (Transform tr) { #if UNITY_EDITOR Refresh(); return tr.transform == activeTransform; #else return false; #endif } } /// /// Every object that wants to draw gizmos should implement this interface. /// See: /// public interface IDrawGizmos { void DrawGizmos(); } public enum DetectedRenderPipeline { BuiltInOrCustom, HDRP, URP } /// /// Global script which draws debug items and gizmos. /// If a Draw.* method has been used or if any script inheriting from the 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. /// [ExecuteAlways] [AddComponentMenu("")] public class DrawingManager : MonoBehaviour { public DrawingData gizmos; static List gizmoDrawers = new List(); static Dictionary gizmoDrawerIndices = new Dictionary(); static DrawingManager _instance; bool framePassed; int lastFrameCount = int.MinValue; float lastFrameTime = -float.NegativeInfinity; int lastFilterFrame; #if UNITY_EDITOR bool builtGizmos; #endif struct GizmoDrawerGroup { public System.Type type; public ProfilerMarker profilerMarker; public List drawers; public bool enabled; } /// True if OnEnable has been called on this instance and OnDisable has not [SerializeField] bool actuallyEnabled; RedrawScope previousFrameRedrawScope; /// /// 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: advanced (view in online documentation for working links) /// public static bool allowRenderToRenderTextures = false; public static bool drawToAllCameras = false; /// /// 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 for that. /// public static float lineWidthMultiplier = 1.0f; CommandBuffer commandBuffer; [System.NonSerialized] DetectedRenderPipeline detectedRenderPipeline = DetectedRenderPipeline.BuiltInOrCustom; #if MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER CustomPass hdrpGlobalPass; #endif #if MODULE_RENDER_PIPELINES_UNIVERSAL HashSet scriptableRenderersWithPass = new HashSet(); 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() 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(); if (Application.isPlaying) DontDestroyOnLoad(go); } /// Detects which render pipeline is being used and configures them for rendering 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 MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER UnityEngine.Assertions.Assert.IsNull(hdrpGlobalPass); hdrpGlobalPass = new AlineHDRPCustomPass(); CustomPassVolume.RegisterGlobalCustomPass(CustomPassInjectionPoint.AfterPostProcess, hdrpGlobalPass); #else if (!_instance.gameObject.TryGetComponent(out CustomPassVolume volume)) { volume = _instance.gameObject.AddComponent(); volume.isGlobal = true; volume.injectionPoint = CustomPassInjectionPoint.AfterPostProcess; volume.customPasses.Add(new AlineHDRPCustomPass()); } #endif 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); } } } #if UNITY_ASSERTIONS && MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER var globalPasses = CustomPassVolume.GetGlobalCustomPasses(CustomPassInjectionPoint.AfterPostProcess); bool found = false; for (int i = 0; i < globalPasses.Count; i++) found |= globalPasses[i].instance == hdrpGlobalPass; UnityEngine.Assertions.Assert.IsTrue(found, "Custom pass for gizmos is not registered. Have the custom passes been forcefully removed by another script?"); #endif return; } #if MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER if (hdrpGlobalPass != null) { CustomPassVolume.UnregisterGlobalCustomPass(CustomPassInjectionPoint.AfterPostProcess, hdrpGlobalPass); hdrpGlobalPass = null; } #endif #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"; detectedRenderPipeline = DetectedRenderPipeline.BuiltInOrCustom; // Callback when rendering with the built-in render pipeline Camera.onPostRender += PostRender; // Callback when rendering with a scriptable render pipeline #if UNITY_2021_1_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 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(); } renderPassFeature.AddRenderPasses(renderer); } } #endif } void OnDisable () { if (!actuallyEnabled) return; actuallyEnabled = false; commandBuffer.Dispose(); commandBuffer = null; Camera.onPostRender -= PostRender; #if UNITY_2021_1_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 #if MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER if (hdrpGlobalPass != null) { CustomPassVolume.UnregisterGlobalCustomPass(CustomPassInjectionPoint.AfterPostProcess, hdrpGlobalPass); hdrpGlobalPass = 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 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(); for (int i = 0; i < gizmoDrawers.Count; i++) { var group = gizmoDrawers[i]; int j = 0; for (int k = 0; k < group.drawers.Count; k++) { var v = group.drawers[k]; if (v as MonoBehaviour) { group.drawers[j] = v; j++; } } group.drawers.RemoveRange(j, group.drawers.Count - j); } MarkerFilterDestroyedObjects.End(); } #if UNITY_EDITOR void DrawGizmos (bool usingRenderPipeline) { GizmoContext.SetDirty(); MarkerGizmosAllowed.Begin(); // Figure out which component types should be rendered for (int i = 0; i < gizmoDrawers.Count; i++) { var group = gizmoDrawers[i]; #if UNITY_2022_1_OR_NEWER // In Unity 2022.1 we can use a new utility class which is more robust. if (GizmoUtility.TryGetGizmoInfo(group.type, out var gizmoInfo)) { group.enabled = gizmoInfo.gizmoEnabled; } else { group.enabled = 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) { group.enabled = false; for (int j = group.drawers.Count - 1; j >= 0; j--) { // Find the first active and enabled drawer if ((group.drawers[j] as MonoBehaviour).isActiveAndEnabled) { group.enabled = ShouldDrawGizmos((UnityEngine.Object)group.drawers[j]); break; } } } else { group.enabled = true; } #endif gizmoDrawers[i] = group; } 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(); var currentStageHandle = currentStage.stageHandle; #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; for (int i = gizmoDrawers.Count - 1; i >= 0; i--) { var group = gizmoDrawers[i]; if (group.enabled && group.drawers.Count > 0) { group.profilerMarker.Begin(); for (int j = group.drawers.Count - 1; j >= 0; j--) { var mono = group.drawers[j] as MonoBehaviour; if (!mono.isActiveAndEnabled || (mono.hideFlags & HideFlags.HideInHierarchy) != 0) continue; #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 && !currentStageHandle.Contains(mono.gameObject); if (disabledDueToIsolationMode) continue; #endif try { group.drawers[j].DrawGizmos(); } catch (System.Exception e) { Debug.LogException(e, mono); } } group.profilerMarker.End(); } } } 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 /// Submit a camera for rendering. /// Indicates if built-in command builders and custom ones without a custom CommandBuilder.cameraTargets should render to this camera. 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(); } /// /// 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). /// public static void Register (IDrawGizmos item) { var tp = item.GetType(); int index; if (gizmoDrawerIndices.TryGetValue(tp, out index)) { } else { // 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. 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); } var mayDrawGizmos = m.DeclaringType != typeof(MonoBehaviourGizmos); if (mayDrawGizmos) { index = gizmoDrawerIndices[tp] = gizmoDrawers.Count; gizmoDrawers.Add(new GizmoDrawerGroup { type = tp, enabled = true, drawers = new List(), profilerMarker = new ProfilerMarker(ProfilerCategory.Render, "Gizmos for " + tp.Name), }); } else { index = gizmoDrawerIndices[tp] = -1; } } if (index == -1) return; gizmoDrawers[index].drawers.Add(item); } /// /// Get an empty builder for queuing drawing commands. /// /// /// // 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); /// } /// /// See: /// /// 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. public static CommandBuilder GetBuilder(bool renderInGame = false) => instance.gizmos.GetBuilder(renderInGame); /// /// Get an empty builder for queuing drawing commands. /// /// See: /// /// Scope for this command builder. See #GetRedrawScope. /// 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. public static CommandBuilder GetBuilder(RedrawScope redrawScope, bool renderInGame = false) => instance.gizmos.GetBuilder(redrawScope, renderInGame); /// /// Get an empty builder for queuing drawing commands. /// TODO: Example usage. /// /// See: /// /// Hash of whatever inputs you used to generate the drawing data. /// Scope for this command builder. See #GetRedrawScope. /// If true, this builder will be rendered in standalone games and in the editor even if gizmos are disabled. public static CommandBuilder GetBuilder(DrawingData.Hasher hasher, RedrawScope redrawScope = default, bool renderInGame = false) => instance.gizmos.GetBuilder(hasher, redrawScope, renderInGame); /// /// A scope which can be used to draw things over multiple frames. /// /// You can use 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. /// /// /// 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(); /// } /// /// /// 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. public static RedrawScope GetRedrawScope (GameObject associatedGameObject = null) { var scope = new RedrawScope(instance.gizmos); scope.DrawUntilDispose(associatedGameObject); return scope; } } }