diff --git a/Runtime/Gizmos/Text.meta b/Runtime/Gizmos/Text.meta new file mode 100644 index 0000000..22f0f23 --- /dev/null +++ b/Runtime/Gizmos/Text.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9709faba0386b5244a8ab4c9c7ce49ec +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Gizmos/Text/DebugXTextSettings.cs b/Runtime/Gizmos/Text/DebugXTextSettings.cs new file mode 100644 index 0000000..ba9a3d1 --- /dev/null +++ b/Runtime/Gizmos/Text/DebugXTextSettings.cs @@ -0,0 +1,98 @@ +using UnityEngine; + +namespace DCFApixels +{ + /// + /// All additional settings for text rendering are stored here. + /// + public readonly struct DebugXTextSettings + { + public const TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.MiddleLeft; + public const int DEFAULT_FONT_SIZE = 16; + + public const float SCREEN_SPACE_SCALE_FACTOR = 0f; + public const float WORLD_SPACE_SCALE_FACTOR = 1f; + + public static readonly DebugXTextSettings Default = new DebugXTextSettings(DEFAULT_FONT_SIZE, DEFAULT_TEXT_ANCHOR, default, 0); + public static readonly DebugXTextSettings WorldSpaceScale = Default.SetWorldSpaceScaleFactor(); + + /// + /// Font size. Default is . + /// + public readonly int FontSize; + + /// + /// Text alignment. Default is . + /// + public readonly TextAnchor TextAnchor; + + public readonly Color BackgroundColor; + public readonly float WorldSpaceScaleFactor; + + // ReSharper disable once UnusedMember.Global + public bool IsHasBackground => BackgroundColor.a > 0; + + public DebugXTextSettings(int fontSize, TextAnchor textAnchor, Color backgroundColor, float worldSpaceScaleFactor) + { + FontSize = fontSize; + TextAnchor = textAnchor; + BackgroundColor = backgroundColor; + WorldSpaceScaleFactor = worldSpaceScaleFactor; + } + + /// + /// Set font size. Default is . + /// + public DebugXTextSettings SetSize(int fontSize) + { + return new DebugXTextSettings(fontSize, TextAnchor, BackgroundColor, WorldSpaceScaleFactor); + } + + /// + /// Sets text alignment. Default is . + /// + public DebugXTextSettings SetAnchor(TextAnchor textAnchor) + { + return new DebugXTextSettings(FontSize, textAnchor, BackgroundColor, WorldSpaceScaleFactor); + } + + /// + /// Sets background image color behind text. Ignored if transparent. + /// + public DebugXTextSettings SetBackground(Color backgroundColor) + { + return new DebugXTextSettings(FontSize, TextAnchor, backgroundColor, WorldSpaceScaleFactor); + } + + /// + /// Synchronizes the text scale in screen space. The text will remain the same size on the screen. + /// + // ReSharper disable once UnusedMember.Global + public DebugXTextSettings SetScreenSpaceScaleFactor() + { + return SetCustomSpaceScaleFactor(SCREEN_SPACE_SCALE_FACTOR); + } + + /// + /// Synchronizes the text scale in world space. The text will remain the same size on the scene. + /// + public DebugXTextSettings SetWorldSpaceScaleFactor() + { + return SetCustomSpaceScaleFactor(WORLD_SPACE_SCALE_FACTOR); + } + + /// + /// Allows you to control the text scale depending on the camera zoom. + /// + /// + ///
+ /// 0 - screen space
+ /// 1 - world space
+ /// Values in between [0.00 - 1.00] blend these spaces together. + /// + public DebugXTextSettings SetCustomSpaceScaleFactor(float factor) + { + return new DebugXTextSettings(FontSize, TextAnchor, BackgroundColor, factor); + } + } +} \ No newline at end of file diff --git a/Runtime/Utils/DebugXTextSettings.cs.meta b/Runtime/Gizmos/Text/DebugXTextSettings.cs.meta similarity index 100% rename from Runtime/Utils/DebugXTextSettings.cs.meta rename to Runtime/Gizmos/Text/DebugXTextSettings.cs.meta diff --git a/Runtime/Gizmos/Text/TextDrawHandlerExtensions.cs b/Runtime/Gizmos/Text/TextDrawHandlerExtensions.cs new file mode 100644 index 0000000..272a8d4 --- /dev/null +++ b/Runtime/Gizmos/Text/TextDrawHandlerExtensions.cs @@ -0,0 +1,34 @@ +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace DCFApixels +{ + using DrawHandler = DebugX.DrawHandler; + using IN = MethodImplAttribute; + + public static class TextDrawHandlerExtensions + { + private const MethodImplOptions LINE = DebugX.LINE; +#if DEBUG + private static bool _singleWarningToggle = true; +#endif + [IN(LINE)] + public static DrawHandler Text(this DrawHandler h, Vector3 position, object text) => h.Text(position, text, DebugXTextSettings.Default); + [IN(LINE)] + public static DrawHandler Text(this DrawHandler h, Vector3 position, object text, DebugXTextSettings settings) + { + if(settings.FontSize <= float.Epsilon) + { +#if DEBUG + if (_singleWarningToggle) + { + Debug.LogWarning("Text rendering requires FontSize > 0, otherwise the text will be invisible. To avoid invalid parameters, use DebugXTextSettings.Default instead of manual instantiation."); + _singleWarningToggle = false; + } +#endif + settings = settings.SetSize(DebugXTextSettings.DEFAULT_FONT_SIZE); + } + return h.Gizmo(new TextGizmo(position, text, settings)); + } + } +} \ No newline at end of file diff --git a/Runtime/Gizmos/Text/TextDrawHandlerExtensions.cs.meta b/Runtime/Gizmos/Text/TextDrawHandlerExtensions.cs.meta new file mode 100644 index 0000000..73073c1 --- /dev/null +++ b/Runtime/Gizmos/Text/TextDrawHandlerExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 16f0ca76a3494c91a2b0912101c88ddd +timeCreated: 1740751022 \ No newline at end of file diff --git a/Runtime/Gizmos/Text/TextGizmo.cs b/Runtime/Gizmos/Text/TextGizmo.cs new file mode 100644 index 0000000..6c70b72 --- /dev/null +++ b/Runtime/Gizmos/Text/TextGizmo.cs @@ -0,0 +1,218 @@ +using DCFApixels.DebugXCore; +using System.Runtime.CompilerServices; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + +namespace DCFApixels +{ + using IN = MethodImplAttribute; + internal readonly struct TextGizmo : IGizmo + { + private const MethodImplOptions LINE = MethodImplOptions.AggressiveInlining; + public readonly Vector3 Position; + public readonly string Text; + public readonly DebugXTextSettings Settings; + [IN(LINE)] + public TextGizmo(Vector3 position, object text, DebugXTextSettings settings) + { + Position = position; + Text = text.ToString(); + Settings = settings; + } + + public IGizmoRenderer RegisterNewRenderer() { return new Renderer(); } + + #region Renderer + private class Renderer : IGizmoRenderer_PostRender + { + private static GUIStyle _labelStyle; + private static GUIContent _labelDummy; + private static Texture2D _whiteTexture; + public int ExecuteOrder => default(UnlitMat).GetExecuteOrder(); + public bool IsStaticRender => false; + public void Prepare(Camera camera, GizmosList list) { } + public void Render(Camera camera, GizmosList list, CommandBuffer cb) { } + public void PostRender(Camera camera, GizmosList list) + { + if (camera == null) { return; } + if (Event.current.type != EventType.Repaint) { return; } + Color dfColor = GUI.color; + InitStatic(); + bool isSceneView = false; + var backgroundMaterial = DebugXAssets.Materials.TextBackground; + +#if UNITY_EDITOR + // TODO: Might replace this with `camera != null && SceneView.GetAllSceneCameras().Any(x => x == camera)`; + // TODO: Not sure if older Unity-versions have GetAllSceneCameras or not. + isSceneView = camera.name == "SceneCamera"; +#endif + + if (isSceneView) + { +#if UNITY_EDITOR + Handles.BeginGUI(); +#endif + } + else + { + GL.PushMatrix(); + GL.LoadPixelMatrix(0, Screen.width, Screen.height, 0); + } + foreach (ref readonly var item in list) + { + _labelDummy.text = item.Value.Text; + GUIStyle style = _labelStyle; + + var zoom = GetCameraZoom(camera, item.Value.Position); + float fontSize = Mathf.Lerp(item.Value.Settings.FontSize, item.Value.Settings.FontSize / zoom, item.Value.Settings.WorldSpaceScaleFactor); + style.fontSize = Mathf.Max(1, Mathf.FloorToInt(fontSize)); + + style.alignment = item.Value.Settings.TextAnchor; + if (!(WorldToGUIPointWithDepth(camera, item.Value.Position).z < 0f)) + { + Rect rect = WorldPointToSizedRect(camera, item.Value.Position, _labelDummy, _labelStyle); + if (item.Value.Settings.IsHasBackground) + { + Color backgroundColor = item.Value.Settings.BackgroundColor * DebugX.GlobalColor; + if(fontSize < 1) + { + backgroundColor.a *= fontSize; + } + Graphics.DrawTexture(rect, _whiteTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, backgroundColor, backgroundMaterial, -1); + } + Color color= item.Color * DebugX.GlobalColor; + if (fontSize < 1) + { + color.a *= fontSize; + } + GUI.color = color; + style.Draw(rect, _labelDummy, false, false, false, false); + } + } + GUI.color = dfColor; + backgroundMaterial.SetColor(DebugX.ColorPropertyID, Color.white); + + if (isSceneView) + { +#if UNITY_EDITOR + Handles.EndGUI(); +#endif + } + else + { + GL.PopMatrix(); + } + } + + #region Init + private void InitStatic() + { + if (_labelStyle == null || _labelDummy == null || _whiteTexture == null) + { + GUIStyleState GenerateGUIStyleState() + { + var result = new GUIStyleState(); + result.textColor = Color.white; + result.background = null; + return result; + } + + // If calling GUI.skin directly - Unity will throw exceptions saying that GUI.skin can be called only from OnSceneGUI context. + GUISkin skin = (GUISkin)typeof(GUI).GetField("s_Skin", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).GetValue(null); //GUI.s_Skin + + _labelStyle = new GUIStyle(skin.label) + { + richText = false, + padding = new RectOffset(1, 1, 0, 0), + margin = new RectOffset(0, 0, 0, 0), + normal = GenerateGUIStyleState(), + active = GenerateGUIStyleState(), + hover = GenerateGUIStyleState(), + focused = GenerateGUIStyleState(), + }; + + _labelDummy = new GUIContent(); + + _whiteTexture = new Texture2D(2, 2); + Color32[] color = new Color32[] + { + Color.white, + Color.white, + Color.white, + Color.white, + }; + _whiteTexture.SetPixels32(color); + _whiteTexture.Apply(); + } + } + #endregion + + #region Utils + public static Vector3 WorldToGUIPointWithDepth(Camera camera, Vector3 world) + { +#if UNITY_EDITOR + world = Handles.matrix.MultiplyPoint(world); +#endif + Vector3 vector = camera.WorldToScreenPoint(world); + vector.y = camera.pixelHeight - vector.y; + Vector2 vector2 = (Vector2)(vector); +#if UNITY_EDITOR + vector2 = EditorGUIUtility.PixelsToPoints(vector); +#endif + return new Vector3(vector2.x, vector2.y, vector.z); + } + public static Rect WorldPointToSizedRect(Camera camera, Vector3 position, GUIContent content, GUIStyle style) + { + Vector2 vector = (Vector2)WorldToGUIPointWithDepth(camera, position); + Vector2 vector2 = style.CalcSize(content); + Rect rect = new Rect(vector.x, vector.y, vector2.x, vector2.y); + switch (style.alignment) + { + case TextAnchor.UpperCenter: + rect.x -= rect.width * 0.5f; + break; + case TextAnchor.UpperRight: + rect.x -= rect.width; + break; + case TextAnchor.MiddleLeft: + rect.y -= rect.height * 0.5f; + break; + case TextAnchor.MiddleCenter: + rect.x -= rect.width * 0.5f; + rect.y -= rect.height * 0.5f; + break; + case TextAnchor.MiddleRight: + rect.x -= rect.width; + rect.y -= rect.height * 0.5f; + break; + case TextAnchor.LowerLeft: + rect.y -= rect.height; + break; + case TextAnchor.LowerCenter: + rect.x -= rect.width * 0.5f; + rect.y -= rect.height; + break; + case TextAnchor.LowerRight: + rect.x -= rect.width; + rect.y -= rect.height; + break; + } + return style.padding.Add(rect); + } + private static float GetCameraZoom(Camera camera, Vector3 position) + { + position = Handles.matrix.MultiplyPoint(position); + Transform cameraTransform = camera.transform; + Vector3 cameraPos = cameraTransform.position; + float z = Vector3.Dot(position - cameraPos, cameraTransform.TransformDirection(new Vector3(0f, 0f, 1f))); + Vector3 pos1 = camera.WorldToScreenPoint(cameraPos + cameraTransform.TransformDirection(new Vector3(0f, 0f, z))); + Vector3 pos2 = camera.WorldToScreenPoint(cameraPos + cameraTransform.TransformDirection(new Vector3(1f, 0f, z))); + float magnitude = (pos1 - pos2).magnitude; + return 80f / Mathf.Max(magnitude, 0.0001f) * EditorGUIUtility.pixelsPerPoint; + } + #endregion + } + #endregion + } +} \ No newline at end of file diff --git a/Runtime/Gizmos/Text/TextGizmo.cs.meta b/Runtime/Gizmos/Text/TextGizmo.cs.meta new file mode 100644 index 0000000..c28c895 --- /dev/null +++ b/Runtime/Gizmos/Text/TextGizmo.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: af700d933ae340608724469c70e8d0fc +timeCreated: 1740750866 \ No newline at end of file diff --git a/Runtime/Utils/DebugXTextSettings.cs b/Runtime/Utils/DebugXTextSettings.cs deleted file mode 100644 index fb5d9f8..0000000 --- a/Runtime/Utils/DebugXTextSettings.cs +++ /dev/null @@ -1,45 +0,0 @@ -using UnityEngine; - -namespace DCFApixels -{ - public readonly struct DebugXTextSettings - { - public const TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.MiddleLeft; - public const int DEFAULT_FONT_SIZE = 16; - public static readonly DebugXTextSettings Default = new DebugXTextSettings(DEFAULT_FONT_SIZE, DEFAULT_TEXT_ANCHOR, default, 0); - public static readonly DebugXTextSettings WorldSpaceScale = Default.SetWorldSpaceScaleFactor(1f); - - public readonly int FontSize; - public readonly TextAnchor TextAnchor; - public readonly Color BackgroundColor; - public readonly float WorldSpaceScaleFactor; - public bool IsHasBackground - { - get { return BackgroundColor.a > 0; } - } - public DebugXTextSettings(int fontSize, TextAnchor textAnchor, Color backgroundColor, float worldSpaceScaleFactor) - { - FontSize = fontSize; - TextAnchor = textAnchor; - BackgroundColor = backgroundColor; - WorldSpaceScaleFactor = worldSpaceScaleFactor; - } - - public DebugXTextSettings SetSize(int fontSize) - { - return new DebugXTextSettings(fontSize, TextAnchor, BackgroundColor, WorldSpaceScaleFactor); - } - public DebugXTextSettings SetAnchor(TextAnchor textAnchor) - { - return new DebugXTextSettings(FontSize, textAnchor, BackgroundColor, WorldSpaceScaleFactor); - } - public DebugXTextSettings SetBackground(Color backgroundColor) - { - return new DebugXTextSettings(FontSize, TextAnchor, backgroundColor, WorldSpaceScaleFactor); - } - public DebugXTextSettings SetWorldSpaceScaleFactor(float factor) - { - return new DebugXTextSettings(FontSize, TextAnchor, BackgroundColor, factor); - } - } -} \ No newline at end of file