1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
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;
}
}
}
|