/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using Leap.Unity.Infix; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; namespace Leap.Unity.RuntimeGizmos { /// /// Have your MonoBehaviour implement this interface to be able to draw runtime gizmos. /// You must also have a RuntimeGizmoManager component in the scene to recieve callbacks. /// public interface IRuntimeGizmoComponent { void OnDrawRuntimeGizmos(RuntimeGizmoDrawer drawer); } [ExecuteInEditMode] public class RuntimeGizmoManager : MonoBehaviour { public const string DEFAULT_SHADER_NAME = "Hidden/Runtime Gizmos"; public const int CIRCLE_RESOLUTION = 32; [Tooltip("Should the gizmos be visible in the game view.")] [SerializeField] protected bool _displayInGameView = true; [Tooltip("Should the gizmos be visible in a build.")] [SerializeField] protected bool _enabledForBuild = true; [Tooltip("The mesh to use for the filled sphere gizmo.")] [SerializeField] protected Mesh _sphereMesh; [Tooltip("The shader to use for rendering gizmos.")] [SerializeField] protected Shader _gizmoShader; protected Mesh _cubeMesh, _wireCubeMesh, _wireSphereMesh; protected static RuntimeGizmoDrawer _backDrawer = null; protected static RuntimeGizmoDrawer _frontDrawer = null; private bool _readyForSwap = false; /// /// Subscribe to this event if you want to draw gizmos after rendering is complete. Doing gizmo /// rendering inside of the normal Camera.onPostRender event will cause rendering artifacts. /// public static event Action OnPostRenderGizmos; /// /// Tries to get a gizmo drawer. Will fail if there is no Gizmo manager in the /// scene, or if it is disabled. /// /// The gizmo matrix will be set to the identity matrix. /// The gizmo color will be set to white. /// public static bool TryGetGizmoDrawer(out RuntimeGizmoDrawer drawer) { drawer = _backDrawer; if (drawer != null) { drawer.ResetMatrixAndColorState(); return true; } else { return false; } } /// /// Tries to get a gizmo drawer for a given gameObject. Will fail if there is no /// gizmo manager in the scene, or if it is disabled. Will also fail if there is /// a disable RuntimeGizmoToggle as a parent of the gameObject. /// /// The gizmo matrix will be set to the identity matrix. /// The gizmo color will be set to white. /// public static bool TryGetGizmoDrawer(GameObject attatchedGameObject, out RuntimeGizmoDrawer drawer) { drawer = _backDrawer; if (drawer != null && !areGizmosDisabled(attatchedGameObject.transform)) { drawer.ResetMatrixAndColorState(); return true; } else { return false; } } protected virtual void OnValidate() { if (_gizmoShader == null) { _gizmoShader = Shader.Find(DEFAULT_SHADER_NAME); } Material tempMat = new Material(_gizmoShader); tempMat.hideFlags = HideFlags.HideAndDontSave; if (tempMat.passCount != 4) { Debug.LogError("Shader " + _gizmoShader + " does not have 4 passes and cannot be used as a gizmo shader."); _gizmoShader = Shader.Find(DEFAULT_SHADER_NAME); } if (_frontDrawer != null && _backDrawer != null) { assignDrawerParams(); } } protected virtual void Reset() { _gizmoShader = Shader.Find(DEFAULT_SHADER_NAME); } protected virtual void OnEnable() { #if !UNITY_EDITOR if (!_enabledForBuild) { enabled = false; return; } #endif _frontDrawer = new RuntimeGizmoDrawer(); _backDrawer = new RuntimeGizmoDrawer(); _frontDrawer.BeginGuard(); if (_gizmoShader == null) { _gizmoShader = Shader.Find(DEFAULT_SHADER_NAME); } generateMeshes(); assignDrawerParams(); //Unsubscribe to prevent double-subscription Camera.onPostRender -= onPostRender; Camera.onPostRender += onPostRender; } protected virtual void OnDisable() { _frontDrawer = null; _backDrawer = null; Camera.onPostRender -= onPostRender; } private List _objList = new List(); private List _gizmoList = new List(); protected virtual void Update() { Scene scene = SceneManager.GetActiveScene(); scene.GetRootGameObjects(_objList); for (int i = 0; i < _objList.Count; i++) { GameObject obj = _objList[i]; obj.GetComponentsInChildren(false, _gizmoList); for (int j = 0; j < _gizmoList.Count; j++) { if (areGizmosDisabled((_gizmoList[j] as Component).transform)) { continue; } _backDrawer.ResetMatrixAndColorState(); try { _gizmoList[j].OnDrawRuntimeGizmos(_backDrawer); } catch (Exception e) { Debug.LogException(e); } } } _readyForSwap = true; } protected void onPostRender(Camera camera) { if ((camera.cullingMask & gameObject.layer) == 0) { return; } #if UNITY_EDITOR //Always draw scene view //Never draw preview or reflection switch (camera.cameraType) { case CameraType.Preview: #if UNITY_2017_1_OR_NEWER case CameraType.Reflection: #endif return; case CameraType.Game: case CameraType.VR: if (!_displayInGameView) { return; } break; } #endif if (_readyForSwap) { if (OnPostRenderGizmos != null) { _backDrawer.ResetMatrixAndColorState(); OnPostRenderGizmos(_backDrawer); } RuntimeGizmoDrawer tempDrawer = _backDrawer; _backDrawer = _frontDrawer; _frontDrawer = tempDrawer; //Guard the front drawer for rendering _frontDrawer.BeginGuard(); //Unguard the back drawer to allow gizmos to be drawn to it _backDrawer.EndGuard(); _readyForSwap = false; _backDrawer.ClearAllGizmos(); } _frontDrawer.DrawAllGizmosToScreen(); } protected static bool areGizmosDisabled(Transform transform) { bool isDisabled = false; do { var toggle = transform.GetComponentInParent(); if (toggle == null) { break; } if (!toggle.enabled) { isDisabled = true; break; } transform = transform.parent; } while (transform != null); return isDisabled; } private void assignDrawerParams() { if (_gizmoShader != null) { _frontDrawer.gizmoShader = _gizmoShader; _backDrawer.gizmoShader = _gizmoShader; } _frontDrawer.sphereMesh = _sphereMesh; _frontDrawer.cubeMesh = _cubeMesh; _frontDrawer.wireSphereMesh = _wireSphereMesh; _frontDrawer.wireCubeMesh = _wireCubeMesh; _backDrawer.sphereMesh = _sphereMesh; _backDrawer.cubeMesh = _cubeMesh; _backDrawer.wireSphereMesh = _wireSphereMesh; _backDrawer.wireCubeMesh = _wireCubeMesh; } private void generateMeshes() { _cubeMesh = new Mesh(); _cubeMesh.name = "RuntimeGizmoCube"; _cubeMesh.hideFlags = HideFlags.HideAndDontSave; List verts = new List(); List indexes = new List(); Vector3[] faces = new Vector3[] { Vector3.forward, Vector3.right, Vector3.up }; for (int i = 0; i < 3; i++) { addQuad(verts, indexes, faces[(i + 0) % 3], -faces[(i + 1) % 3], faces[(i + 2) % 3]); addQuad(verts, indexes, -faces[(i + 0) % 3], faces[(i + 1) % 3], faces[(i + 2) % 3]); } _cubeMesh.SetVertices(verts); _cubeMesh.SetIndices(indexes.ToArray(), MeshTopology.Quads, 0); _cubeMesh.RecalculateNormals(); _cubeMesh.RecalculateBounds(); _cubeMesh.UploadMeshData(true); _wireCubeMesh = new Mesh(); _wireCubeMesh.name = "RuntimeWireCubeMesh"; _wireCubeMesh.hideFlags = HideFlags.HideAndDontSave; verts.Clear(); indexes.Clear(); for (int dx = 1; dx >= -1; dx -= 2) { for (int dy = 1; dy >= -1; dy -= 2) { for (int dz = 1; dz >= -1; dz -= 2) { verts.Add(0.5f * new Vector3(dx, dy, dz)); } } } addCorner(indexes, 0, 1, 2, 4); addCorner(indexes, 3, 1, 2, 7); addCorner(indexes, 5, 1, 4, 7); addCorner(indexes, 6, 2, 4, 7); _wireCubeMesh.SetVertices(verts); _wireCubeMesh.SetIndices(indexes.ToArray(), MeshTopology.Lines, 0); _wireCubeMesh.RecalculateBounds(); _wireCubeMesh.UploadMeshData(true); _wireSphereMesh = new Mesh(); _wireSphereMesh.name = "RuntimeWireSphereMesh"; _wireSphereMesh.hideFlags = HideFlags.HideAndDontSave; verts.Clear(); indexes.Clear(); int totalVerts = CIRCLE_RESOLUTION * 3; for (int i = 0; i < CIRCLE_RESOLUTION; i++) { float angle = Mathf.PI * 2 * i / CIRCLE_RESOLUTION; float dx = 0.5f * Mathf.Cos(angle); float dy = 0.5f * Mathf.Sin(angle); for (int j = 0; j < 3; j++) { indexes.Add((i * 3 + j + 0) % totalVerts); indexes.Add((i * 3 + j + 3) % totalVerts); } verts.Add(new Vector3(dx, dy, 0)); verts.Add(new Vector3(0, dx, dy)); verts.Add(new Vector3(dx, 0, dy)); } _wireSphereMesh.SetVertices(verts); _wireSphereMesh.SetIndices(indexes.ToArray(), MeshTopology.Lines, 0); _wireSphereMesh.RecalculateBounds(); _wireSphereMesh.UploadMeshData(true); } private void addQuad(List verts, List indexes, Vector3 normal, Vector3 axis1, Vector3 axis2) { indexes.Add(verts.Count + 0); indexes.Add(verts.Count + 1); indexes.Add(verts.Count + 2); indexes.Add(verts.Count + 3); verts.Add(0.5f * (normal + axis1 + axis2)); verts.Add(0.5f * (normal + axis1 - axis2)); verts.Add(0.5f * (normal - axis1 - axis2)); verts.Add(0.5f * (normal - axis1 + axis2)); } private void addCorner(List indexes, int a, int b, int c, int d) { indexes.Add(a); indexes.Add(b); indexes.Add(a); indexes.Add(c); indexes.Add(a); indexes.Add(d); } } public class RuntimeGizmoDrawer { public const int UNLIT_SOLID_PASS = 0; public const int UNLIT_TRANSPARENT_PASS = 1; public const int SHADED_SOLID_PASS = 2; public const int SHADED_TRANSPARENT_PASS = 3; private List _operations = new List(); private List _matrices = new List(); private List _colors = new List(); private List _lines = new List(); private List _wireSpheres = new List(); private List _meshes = new List(); private Color _currColor = Color.white; private Matrix4x4 _currMatrix = Matrix4x4.identity; private Stack _matrixStack = new Stack(); private bool _isInWireMode = false; private Material _gizmoMaterial; private int _operationCountOnGuard = -1; public Shader gizmoShader { get { if (_gizmoMaterial == null) { return null; } else { return _gizmoMaterial.shader; } } set { if (_gizmoMaterial == null) { _gizmoMaterial = new Material(value); _gizmoMaterial.name = "Runtime Gizmo Material"; _gizmoMaterial.hideFlags = HideFlags.HideAndDontSave; } else { _gizmoMaterial.shader = value; } } } public Mesh cubeMesh, wireCubeMesh, sphereMesh, wireSphereMesh; /// /// Begins a draw-guard. If any gizmos are drawn to this drawer an exception will be thrown at the end of the guard. /// public void BeginGuard() { _operationCountOnGuard = _operations.Count; } /// /// Ends a draw-guard. If any gizmos were drawn to this drawer during the guard, an exception will be thrown. /// public void EndGuard() { bool wereGizmosDrawn = _operations.Count > _operationCountOnGuard; _operationCountOnGuard = -1; if (wereGizmosDrawn) { Debug.LogError("New gizmos were drawn to the front buffer! Make sure to never keep a reference to a Drawer, always get a new one every time you want to start drawing."); } } /// /// Causes all remaining gizmos drawing to be done in the local coordinate space of the given transform. /// public void RelativeTo(Transform transform) { matrix = transform.localToWorldMatrix; } /// /// Saves the current gizmo matrix to the gizmo matrix stack. /// public void PushMatrix() { _matrixStack.Push(_currMatrix); } /// /// Restores the current gizmo matrix from the gizmo matrix stack. /// public void PopMatrix() { matrix = _matrixStack.Pop(); } /// /// Resets the matrix to the identity matrix and the color to white. /// public void ResetMatrixAndColorState() { matrix = Matrix4x4.identity; color = Color.white; } /// /// Sets or gets the color for the gizmos that will be drawn next. /// public Color color { get { return _currColor; } set { if (_currColor == value) { return; } _currColor = value; _operations.Add(OperationType.SetColor); _colors.Add(_currColor); } } /// /// Sets or gets the matrix used to transform all gizmos. /// public Matrix4x4 matrix { get { return _currMatrix; } set { if (_currMatrix == value) { return; } _currMatrix = value; _operations.Add(OperationType.SetMatrix); _matrices.Add(_currMatrix); } } /// /// Draw a filled gizmo mesh using the given matrix transform. /// public void DrawMesh(Mesh mesh, Matrix4x4 matrix) { setWireMode(false); drawMeshInternal(mesh, matrix); } /// /// Draws a filled gizmo mesh at the given transform location. /// public void DrawMesh(Mesh mesh, Vector3 position, Quaternion rotation, Vector3 scale) { DrawMesh(mesh, Matrix4x4.TRS(position, rotation, scale)); } /// /// Draws a wire gizmo mesh using the given matrix transform. /// public void DrawWireMesh(Mesh mesh, Matrix4x4 matrix) { setWireMode(true); drawMeshInternal(mesh, matrix); } /// /// Draws a wire gizmo mesh at the given transform location. /// public void DrawWireMesh(Mesh mesh, Vector3 position, Quaternion rotation, Vector3 scale) { DrawWireMesh(mesh, Matrix4x4.TRS(position, rotation, scale)); } /// /// Draws a gizmo line that connects the two positions. /// public void DrawLine(Vector3 a, Vector3 b) { _operations.Add(OperationType.DrawLine); _lines.Add(new Line(a, b)); } /// /// Draws a filled gizmo cube at the given position with the given size. /// public void DrawCube(Vector3 position, Vector3 size) { DrawMesh(cubeMesh, position, Quaternion.identity, size); } /// /// Draws a wire gizmo cube at the given position with the given size. /// public void DrawWireCube(Vector3 position, Vector3 size) { DrawWireMesh(wireCubeMesh, position, Quaternion.identity, size); } /// /// Draws a filled gizmo sphere at the given position with the given radius. /// public void DrawSphere(Vector3 center, float radius) { //Throw an error here so we can give a more specific error than the more //general one which will be thrown later for a null mesh. if (sphereMesh == null) { throw new InvalidOperationException("Cannot draw a sphere because the Runtime Gizmo Manager does not have a sphere mesh assigned!"); } DrawMesh(sphereMesh, center, Quaternion.identity, Vector3.one * radius * 2); } public void DrawWireSphere(Pose pose, float radius, int numSegments = 32) { _operations.Add(OperationType.DrawWireSphere); _wireSpheres.Add(new WireSphere() { pose = pose, radius = radius, numSegments = numSegments }); } /// /// Draws a wire gizmo sphere at the given position with the given radius. /// public void DrawWireSphere(Vector3 center, float radius, int numSegments = 32) { DrawWireSphere(new Pose(center, Quaternion.identity), radius, numSegments); } /// /// Draws a wire ellipsoid gizmo with two specified foci and a specified minor axis /// length. /// public void DrawEllipsoid(Vector3 foci1, Vector3 foci2, float minorAxis) { PushMatrix(); Vector3 ellipseCenter = (foci1 + foci2) / 2f; Quaternion ellipseRotation = Quaternion.LookRotation(foci1 - foci2); var majorAxis = Mathf.Sqrt(Mathf.Pow(Vector3.Distance(foci1, foci2) / 2f, 2f) + Mathf.Pow(minorAxis / 2f, 2f)) * 2f; Vector3 ellipseScale = new Vector3(minorAxis, minorAxis, majorAxis); matrix = Matrix4x4.TRS(ellipseCenter, ellipseRotation, ellipseScale); DrawWireSphere(Vector3.zero, 0.5f); PopMatrix(); } /// /// Draws a wire gizmo capsule at the given position, with the given start and end /// points and radius. /// public void DrawWireCapsule(Vector3 start, Vector3 end, float radius) { Vector3 up = (end - start).normalized * radius; Vector3 forward = Vector3.Slerp(up, -up, 0.5F); Vector3 right = Vector3.Cross(up, forward).normalized * radius; float height = (start - end).magnitude; // Radial circles DrawLineWireCircle(start, up, radius, 8); DrawLineWireCircle(end, -up, radius, 8); // Sides DrawLine(start + right, end + right); DrawLine(start - right, end - right); DrawLine(start + forward, end + forward); DrawLine(start - forward, end - forward); // Endcaps DrawWireArc(start, right, forward, radius, 0.5F, 8); DrawWireArc(start, forward, -right, radius, 0.5F, 8); DrawWireArc(end, right, -forward, radius, 0.5F, 8); DrawWireArc(end, forward, right, radius, 0.5F, 8); } private void DrawLineWireCircle(Vector3 center, Vector3 normal, float radius, int numCircleSegments = 16) { DrawWireArc(center, normal, Vector3.Slerp(normal, -normal, 0.5F), radius, 1.0F, numCircleSegments); } public void DrawWireArc(Vector3 center, Vector3 normal, Vector3 radialStartDirection, float radius, float fractionOfCircleToDraw, int numCircleSegments = 16) { normal = normal.normalized; Vector3 radiusVector = radialStartDirection.normalized * radius; Vector3 nextVector; int numSegmentsToDraw = (int)(numCircleSegments * fractionOfCircleToDraw); Quaternion rotator = Quaternion.AngleAxis(360f / numCircleSegments, normal); for (int i = 0; i < numSegmentsToDraw; i++) { nextVector = rotator * radiusVector; DrawLine(center + radiusVector, center + nextVector); radiusVector = nextVector; } } private List _colliderList = new List(); public void DrawColliders(GameObject gameObject, bool useWireframe = true, bool traverseHierarchy = true, bool drawTriggers = false) { PushMatrix(); if (traverseHierarchy) { gameObject.GetComponentsInChildren(_colliderList); } else { gameObject.GetComponents(_colliderList); } for (int i = 0; i < _colliderList.Count; i++) { Collider collider = _colliderList[i]; RelativeTo(collider.transform); if (collider.isTrigger && !drawTriggers) { continue; } DrawCollider(collider, skipMatrixSetup: true); } PopMatrix(); } public void DrawCollider(Collider collider, bool useWireframe = true, bool skipMatrixSetup = false) { if (!skipMatrixSetup) { PushMatrix(); RelativeTo(collider.transform); } if (collider is BoxCollider) { BoxCollider box = collider as BoxCollider; if (useWireframe) { DrawWireCube(box.center, box.size); } else { DrawCube(box.center, box.size); } } else if (collider is SphereCollider) { SphereCollider sphere = collider as SphereCollider; if (useWireframe) { DrawWireSphere(sphere.center, sphere.radius); } else { DrawSphere(sphere.center, sphere.radius); } } else if (collider is CapsuleCollider) { CapsuleCollider capsule = collider as CapsuleCollider; if (useWireframe) { Vector3 capsuleDir; switch (capsule.direction) { case 0: capsuleDir = Vector3.right; break; case 1: capsuleDir = Vector3.up; break; case 2: default: capsuleDir = Vector3.forward; break; } DrawWireCapsule(capsule.center + capsuleDir * (capsule.height / 2F - capsule.radius), capsule.center - capsuleDir * (capsule.height / 2F - capsule.radius), capsule.radius); } else { Vector3 size = Vector3.zero; size += Vector3.one * capsule.radius * 2; size += new Vector3(capsule.direction == 0 ? 1 : 0, capsule.direction == 1 ? 1 : 0, capsule.direction == 2 ? 1 : 0) * (capsule.height - capsule.radius * 2); DrawCube(capsule.center, size); } } else if (collider is MeshCollider) { MeshCollider mesh = collider as MeshCollider; if (mesh.sharedMesh != null) { if (useWireframe) { DrawWireMesh(mesh.sharedMesh, Matrix4x4.identity); } else { DrawMesh(mesh.sharedMesh, Matrix4x4.identity); } } } if (!skipMatrixSetup) { PopMatrix(); } } /// /// Draws a simple XYZ-cross position gizmo at the target position, whose size is /// scaled relative to the main camera's distance to the target position (for reliable /// visibility). /// /// Or, if you provide an override scale, you can enforce a radius size for the gizmo. /// /// You can also provide a color argument and lerp coefficient towards that color from /// the axes' default colors (red, green, blue). Colors are lerped in HSV space. /// public void DrawPosition(Vector3 pos, Color lerpColor, float lerpCoeff, float? overrideScale = null) { float targetScale; if (overrideScale.HasValue) { targetScale = overrideScale.Value; } else { targetScale = 0.06f; // 6 cm at 1m away. var curCam = Camera.current; var posWorldSpace = matrix * pos; if (curCam != null) { float camDistance = Vector3.Distance(posWorldSpace, curCam.transform.position); targetScale *= camDistance; } } float extent = (targetScale / 2f); float negativeAlpha = 0.6f; color = Color.red; if (lerpCoeff != 0f) { color = color.LerpHSV(lerpColor, lerpCoeff); } DrawLine(pos, pos + Vector3.right * extent); color = Color.black.WithAlpha(negativeAlpha); if (lerpCoeff != 0f) { color = color.LerpHSV(lerpColor, lerpCoeff); } DrawLine(pos, pos - Vector3.right * extent); color = Color.green; if (lerpCoeff != 0f) { color = color.LerpHSV(lerpColor, lerpCoeff); } DrawLine(pos, pos + Vector3.up * extent); color = Color.black.WithAlpha(negativeAlpha); if (lerpCoeff != 0f) { color = color.LerpHSV(lerpColor, lerpCoeff); } DrawLine(pos, pos - Vector3.up * extent); color = Color.blue; if (lerpCoeff != 0f) { color = color.LerpHSV(lerpColor, lerpCoeff); } DrawLine(pos, pos + Vector3.forward * extent); color = Color.black.WithAlpha(negativeAlpha); if (lerpCoeff != 0f) { color = color.LerpHSV(lerpColor, lerpCoeff); } DrawLine(pos, pos - Vector3.forward * extent); } /// /// Draws a simple XYZ-cross position gizmo at the target position, whose size is /// scaled relative to the main camera's distance to the target position (for reliable /// visibility). /// public void DrawPosition(Vector3 pos) { DrawPosition(pos, Color.white, 0f); } public void DrawPosition(Vector3 pos, float overrideScale) { DrawPosition(pos, Color.white, 0f, overrideScale); } public void DrawRect(Transform frame, Rect rect) { PushMatrix(); this.matrix = frame.localToWorldMatrix; DrawLine(rect.Corner00(), rect.Corner01()); DrawLine(rect.Corner01(), rect.Corner11()); DrawLine(rect.Corner11(), rect.Corner10()); DrawLine(rect.Corner10(), rect.Corner00()); PopMatrix(); } public void ClearAllGizmos() { _operations.Clear(); _matrices.Clear(); _colors.Clear(); _lines.Clear(); _wireSpheres.Clear(); _meshes.Clear(); _isInWireMode = false; _currMatrix = Matrix4x4.identity; _currColor = Color.white; } public void DrawAllGizmosToScreen() { try { int matrixIndex = 0; int colorIndex = 0; int lineIndex = 0; int wireSphereIndex = 0; int meshIndex = 0; int currPass = -1; _currMatrix = Matrix4x4.identity; _currColor = Color.white; GL.wireframe = false; for (int i = 0; i < _operations.Count; i++) { OperationType type = _operations[i]; switch (type) { case OperationType.SetMatrix: _currMatrix = _matrices[matrixIndex++]; break; case OperationType.SetColor: _currColor = _colors[colorIndex++]; currPass = -1; //force pass to be set the next time we need to draw break; case OperationType.ToggleWireframe: GL.wireframe = !GL.wireframe; break; case OperationType.DrawLine: setPass(ref currPass, isUnlit: true); GL.Begin(GL.LINES); Line line = _lines[lineIndex++]; GL.Vertex(_currMatrix.MultiplyPoint(line.a)); GL.Vertex(_currMatrix.MultiplyPoint(line.b)); GL.End(); break; case OperationType.DrawWireSphere: setPass(ref currPass, isUnlit: true); GL.Begin(GL.LINES); WireSphere wireSphere = _wireSpheres[wireSphereIndex++]; drawWireSphereNow(wireSphere, ref currPass); GL.End(); break; case OperationType.DrawMesh: if (GL.wireframe) { setPass(ref currPass, isUnlit: true); } else { setPass(ref currPass, isUnlit: false); } Graphics.DrawMeshNow(_meshes[meshIndex++], _currMatrix * _matrices[matrixIndex++]); break; default: throw new InvalidOperationException("Unexpected operation type " + type); } } } finally { GL.wireframe = false; } } private void drawLineNow(Vector3 a, Vector3 b) { GL.Vertex(_currMatrix.MultiplyPoint(a)); GL.Vertex(_currMatrix.MultiplyPoint(b)); } private void drawWireArcNow(Vector3 center, Vector3 normal, Vector3 radialStartDirection, float radius, float fractionOfCircleToDraw, int numCircleSegments = 16) { normal = normal.normalized; Vector3 radiusVector = radialStartDirection.normalized * radius; Vector3 nextVector; int numSegmentsToDraw = (int)(numCircleSegments * fractionOfCircleToDraw); Quaternion rotator = Quaternion.AngleAxis(360f / numCircleSegments, normal); for (int i = 0; i < numSegmentsToDraw; i++) { nextVector = rotator * radiusVector; drawLineNow(center + radiusVector, center + nextVector); radiusVector = nextVector; } } private void setCurrentPassColorIfNew(Color desiredColor, ref int curPass) { if (_currColor != desiredColor) { _currColor = desiredColor; setPass(ref curPass, isUnlit: true); } } private void drawPlaneSoftenedWireArcNow(Vector3 position, Vector3 circleNormal, Vector3 radialStartDirection, float radius, Color inFrontOfPlaneColor, Color behindPlaneColor, Vector3 planeNormal, ref int curPass, float fractionOfCircleToDraw = 1.0f, int numCircleSegments = 16) { var origCurrColor = _currColor; var onPlaneDir = planeNormal.Cross(circleNormal); var Q = Quaternion.AngleAxis(360f / numCircleSegments, circleNormal); var r = radialStartDirection * radius; for (int i = 0; i < numCircleSegments + 1; i++) { var nextR = Q * r; var onPlaneAngle = Infix.Infix.SignedAngle(r, onPlaneDir, circleNormal); var nextOnPlaneAngle = Infix.Infix.SignedAngle(nextR, onPlaneDir, circleNormal); var front = onPlaneAngle < 0; var nextFront = nextOnPlaneAngle < 0; if (front != nextFront) { var targetColor = Color.Lerp(inFrontOfPlaneColor, behindPlaneColor, 0.5f); GL.End(); setPass(ref curPass, isUnlit: true, desiredCurrColor: targetColor); GL.Begin(GL.LINES); } else if (front) { var targetColor = inFrontOfPlaneColor; GL.End(); setPass(ref curPass, isUnlit: true, desiredCurrColor: targetColor); GL.Begin(GL.LINES); } else { var targetColor = behindPlaneColor; GL.End(); setPass(ref curPass, isUnlit: true, desiredCurrColor: targetColor); GL.Begin(GL.LINES); } drawLineNow(r, nextR); r = nextR; } _currColor = origCurrColor; } private void drawWireSphereNow(WireSphere wireSphere, ref int curPass) { var position = wireSphere.pose.position; var rotation = wireSphere.pose.rotation; var worldPosition = _currMatrix.MultiplyPoint3x4(position); var dirToCamera = (Camera.current.transform.position - worldPosition).normalized; var dirToCameraInMatrix = _currMatrix.inverse.MultiplyVector(dirToCamera); // Wire sphere outline. This is just a wire sphere that faces the camera. drawWireArcNow(position, dirToCameraInMatrix, dirToCameraInMatrix.Perpendicular(), wireSphere.radius, 1.0f, numCircleSegments: wireSphere.numSegments); var x = rotation * Vector3.right; var y = rotation * Vector3.up; var z = rotation * Vector3.forward; drawPlaneSoftenedWireArcNow(position, y, x, wireSphere.radius, inFrontOfPlaneColor: _currColor, behindPlaneColor: _currColor.WithAlpha(_currColor.a * 0.1f), planeNormal: dirToCameraInMatrix, curPass: ref curPass, fractionOfCircleToDraw: 1.0f, numCircleSegments: wireSphere.numSegments); drawPlaneSoftenedWireArcNow(position, z, y, wireSphere.radius, inFrontOfPlaneColor: _currColor, behindPlaneColor: _currColor.WithAlpha(_currColor.a * 0.1f), planeNormal: dirToCameraInMatrix, curPass: ref curPass, fractionOfCircleToDraw: 1.0f, numCircleSegments: wireSphere.numSegments); drawPlaneSoftenedWireArcNow(position, x, z, wireSphere.radius, inFrontOfPlaneColor: _currColor, behindPlaneColor: _currColor.WithAlpha(_currColor.a * 0.1f), planeNormal: dirToCameraInMatrix, curPass: ref curPass, fractionOfCircleToDraw: 1.0f, numCircleSegments: wireSphere.numSegments); } private void setPass(ref int currPass, bool isUnlit, Color? desiredCurrColor = null) { bool needToUpdateColor = false; if (desiredCurrColor.HasValue) { needToUpdateColor = _currColor != desiredCurrColor.Value; _currColor = desiredCurrColor.Value; } int newPass; if (isUnlit) { if (_currColor.a < 1) { newPass = UNLIT_TRANSPARENT_PASS; } else { newPass = UNLIT_SOLID_PASS; } } else { if (_currColor.a < 1) { newPass = SHADED_TRANSPARENT_PASS; } else { newPass = SHADED_SOLID_PASS; } } if (currPass != newPass || needToUpdateColor) { currPass = newPass; _gizmoMaterial.color = _currColor; _gizmoMaterial.SetPass(currPass); } } private void drawMeshInternal(Mesh mesh, Matrix4x4 matrix) { if (mesh == null) { throw new InvalidOperationException("Mesh cannot be null!"); } _operations.Add(OperationType.DrawMesh); _meshes.Add(mesh); _matrices.Add(matrix); } private void setWireMode(bool wireMode) { if (_isInWireMode != wireMode) { _operations.Add(OperationType.ToggleWireframe); _isInWireMode = wireMode; } } private enum OperationType { SetMatrix, ToggleWireframe, SetColor, DrawLine, DrawWireSphere, DrawMesh } private struct Line { public Vector3 a, b; public Line(Vector3 a, Vector3 b) { this.a = a; this.b = b; } } private struct WireSphere { public Pose pose; public float radius; public int numSegments; } } public static class RuntimeGizmoExtensions { public static void DrawPose(this RuntimeGizmos.RuntimeGizmoDrawer drawer, Pose pose, float radius = 0.10f, bool drawCube = false) { drawer.PushMatrix(); drawer.matrix = Matrix4x4.TRS(pose.position, pose.rotation, Vector3.one); var origColor = drawer.color; //drawer.DrawWireSphere(Vector3.zero, radius); if (drawCube) { drawer.DrawCube(Vector3.zero, Vector3.one * radius * 0.3f); } drawer.DrawPosition(Vector3.zero, radius * 2); drawer.color = origColor; drawer.PopMatrix(); } public static void DrawRay(this RuntimeGizmos.RuntimeGizmoDrawer drawer, Vector3 position, Vector3 direction) { drawer.DrawLine(position, position + direction); } } }