quantum-space-buddies/QSB/Utility/Popcron.Gizmos/GizmosInstance.cs
Mister_Nebula 4e65e500ff cleanup
2021-01-05 15:58:12 +00:00

393 lines
8.0 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
#if !UNITY_2019_1_OR_NEWER
public struct ScriptableRenderContext { }
public static class RenderPipelineManager
{
public static event Action<ScriptableRenderContext, Camera> endCameraRendering;
}
#endif
namespace Popcron
{
[ExecuteInEditMode]
[AddComponentMenu("")]
public class GizmosInstance : MonoBehaviour
{
private const int DefaultQueueSize = 4096;
private static GizmosInstance instance;
private static bool hotReloaded = true;
private static Material defaultMaterial;
private static Plane[] cameraPlanes = new Plane[6];
private Material overrideMaterial;
private int queueIndex = 0;
private int lastFrame;
private Element[] queue = new Element[DefaultQueueSize];
/// <summary>
/// The material being used to render
/// </summary>
public static Material Material
{
get
{
var inst = GetOrCreate();
if (inst.overrideMaterial)
{
return inst.overrideMaterial;
}
return DefaultMaterial;
}
set
{
var inst = GetOrCreate();
inst.overrideMaterial = value;
}
}
/// <summary>
/// The default line renderer material
/// </summary>
public static Material DefaultMaterial
{
get
{
if (!defaultMaterial)
{
// Unity has a built-in shader that is useful for drawing
// simple colored things.
var shader = Shader.Find("Hidden/Internal-Colored");
defaultMaterial = new Material(shader)
{
hideFlags = HideFlags.HideAndDontSave
};
// Turn on alpha blending
defaultMaterial.SetInt("_SrcBlend", (int)BlendMode.SrcAlpha);
defaultMaterial.SetInt("_DstBlend", (int)BlendMode.OneMinusSrcAlpha);
defaultMaterial.SetInt("_Cull", (int)CullMode.Off);
defaultMaterial.SetInt("_ZWrite", 0);
}
return defaultMaterial;
}
}
internal static GizmosInstance GetOrCreate()
{
if (hotReloaded || !instance)
{
var markDirty = false;
var gizmosInstances = FindObjectsOfType<GizmosInstance>();
for (var i = 0; i < gizmosInstances.Length; i++)
{
instance = gizmosInstances[i];
//destroy any extra gizmo instances
if (i > 0)
{
if (Application.isPlaying)
{
Destroy(gizmosInstances[i]);
}
else
{
DestroyImmediate(gizmosInstances[i]);
markDirty = true;
}
}
}
//none were found, create a new one
if (!instance)
{
instance = new GameObject(typeof(GizmosInstance).FullName).AddComponent<GizmosInstance>();
instance.gameObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
markDirty = true;
}
#if UNITY_EDITOR
//mark scene as dirty
if (markDirty && !Application.isPlaying)
{
Scene scene = SceneManager.GetActiveScene();
EditorSceneManager.MarkSceneDirty(scene);
}
#endif
hotReloaded = false;
}
return instance;
}
private float CurrentTime
{
get
{
var time = 0f;
if (Application.isPlaying)
{
time = Time.time;
}
else
{
#if UNITY_EDITOR
time = (float)EditorApplication.timeSinceStartup;
#endif
}
return time;
}
}
/// <summary>
/// Submits an array of points to draw into the queue.
/// </summary>
internal static void Submit(Vector3[] points, Color? color, bool dashed)
{
var inst = GetOrCreate();
//if new frame, reset index
if (inst.lastFrame != Time.frameCount)
{
inst.lastFrame = Time.frameCount;
inst.queueIndex = 0;
}
//excedeed the length, so make it even bigger
if (inst.queueIndex >= inst.queue.Length)
{
var bigger = new Element[inst.queue.Length + DefaultQueueSize];
for (var i = inst.queue.Length; i < bigger.Length; i++)
{
bigger[i] = new Element();
}
Array.Copy(inst.queue, 0, bigger, 0, inst.queue.Length);
inst.queue = bigger;
}
inst.queue[inst.queueIndex].color = color ?? Color.white;
inst.queue[inst.queueIndex].points = points;
inst.queue[inst.queueIndex].dashed = dashed;
inst.queueIndex++;
}
private void OnEnable()
{
//populate queue with empty elements
queue = new Element[DefaultQueueSize];
for (var i = 0; i < DefaultQueueSize; i++)
{
queue[i] = new Element();
}
if (GraphicsSettings.renderPipelineAsset == null)
{
Camera.onPostRender += OnRendered;
}
else
{
RenderPipelineManager.endCameraRendering += OnRendered;
}
}
private void OnDisable()
{
if (GraphicsSettings.renderPipelineAsset == null)
{
Camera.onPostRender -= OnRendered;
}
else
{
RenderPipelineManager.endCameraRendering -= OnRendered;
}
}
private void OnRendered(ScriptableRenderContext context, Camera camera) => OnRendered(camera);
private bool ShouldRenderCamera(Camera camera)
{
if (!camera)
{
return false;
}
//allow the scene and main camera always
var isSceneCamera = false;
#if UNITY_EDITOR
SceneView sceneView = SceneView.currentDrawingSceneView;
if (sceneView == null)
{
sceneView = SceneView.lastActiveSceneView;
}
if (sceneView != null && sceneView.camera == camera)
{
isSceneCamera = true;
}
#endif
if (isSceneCamera || camera.CompareTag("MainCamera"))
{
return true;
}
//it passed through the filter
if (Gizmos.CameraFilter?.Invoke(camera) == true)
{
return true;
}
return false;
}
private bool IsVisibleByCamera(Element points, Camera camera)
{
if (!camera)
{
return false;
}
//essentially check if at least 1 point is visible by the camera
for (var i = 0; i < points.points.Length; i++)
{
var vp = camera.WorldToViewportPoint(points.points[i]);
if (vp.x >= 0 && vp.x <= 1 && vp.y >= 0 && vp.y <= 1)
{
return true;
}
}
return false;
}
private void Update()
{
//always render something
Gizmos.Line(default, default);
}
private void OnRendered(Camera camera)
{
Material.SetPass(Gizmos.Pass);
//shouldnt be rendering
if (!Gizmos.Enabled)
{
queueIndex = 0;
}
//check if this camera is ok to render with
if (!ShouldRenderCamera(camera))
{
GL.PushMatrix();
GL.Begin(GL.LINES);
//bla bla bla
GL.End();
GL.PopMatrix();
return;
}
var offset = Gizmos.Offset;
GL.PushMatrix();
GL.MultMatrix(Matrix4x4.identity);
GL.Begin(GL.LINES);
var alt = CurrentTime % 1 > 0.5f;
var dashGap = Mathf.Clamp(Gizmos.DashGap, 0.01f, 32f);
var frustumCull = Gizmos.FrustumCulling;
var points = new List<Vector3>();
//draw le elements
for (var e = 0; e < queueIndex; e++)
{
//just in case
if (queue.Length <= e)
{
break;
}
var element = queue[e];
//dont render this thingy if its not inside the frustum
if (frustumCull)
{
if (!IsVisibleByCamera(element, camera))
{
continue;
}
}
points.Clear();
if (element.dashed)
{
//subdivide
for (var i = 0; i < element.points.Length - 1; i++)
{
var pointA = element.points[i];
var pointB = element.points[i + 1];
var direction = pointB - pointA;
if (direction.sqrMagnitude > dashGap * dashGap * 2f)
{
var magnitude = direction.magnitude;
var amount = Mathf.RoundToInt(magnitude / dashGap);
direction /= magnitude;
for (var p = 0; p < amount - 1; p++)
{
if (p % 2 == (alt ? 1 : 0))
{
var startLerp = p / (amount - 1f);
var endLerp = (p + 1) / (amount - 1f);
var start = Vector3.Lerp(pointA, pointB, startLerp);
var end = Vector3.Lerp(pointA, pointB, endLerp);
points.Add(start);
points.Add(end);
}
}
}
else
{
points.Add(pointA);
points.Add(pointB);
}
}
}
else
{
points.AddRange(element.points);
}
GL.Color(element.color);
for (var i = 0; i < points.Count; i++)
{
GL.Vertex(points[i] + offset);
}
}
GL.End();
GL.PopMatrix();
}
}
}