#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;
}
}
}