diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll index e182680..2db784f 100644 Binary files a/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index 7def1b9..6d11eb6 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -100,7 +100,6 @@ - diff --git a/Source/ConformalDecals/Text/DecalFont.cs b/Source/ConformalDecals/Text/DecalFont.cs index 45ec8f7..efa0b6e 100644 --- a/Source/ConformalDecals/Text/DecalFont.cs +++ b/Source/ConformalDecals/Text/DecalFont.cs @@ -6,13 +6,17 @@ using UniLinq; namespace ConformalDecals.Text { public class DecalFont : IEquatable { + /// Human-readable name for the font public string Title { get; } - public TMP_FontAsset FontAsset { get; } - + /// Internal name for the font public string Name => FontAsset.name; + /// The font asset itself + public TMP_FontAsset FontAsset { get; } + /// Styles that are forced on for this font, + /// e.g. smallcaps for a font without lower case characters public FontStyles FontStyle { get; } public bool Bold => (FontStyle & FontStyles.Bold) != 0; @@ -23,7 +27,8 @@ namespace ConformalDecals.Text { public bool SmallCaps => (FontStyle & FontStyles.SmallCaps) != 0; - + /// Styles that are forced off for this font, + /// e.g. underline for a font with no underscore character public FontStyles FontStyleMask { get; } public bool BoldMask => (FontStyleMask & FontStyles.Bold) != 0; diff --git a/Source/ConformalDecals/Text/DecalText.cs b/Source/ConformalDecals/Text/DecalText.cs index 07ff603..c974292 100644 --- a/Source/ConformalDecals/Text/DecalText.cs +++ b/Source/ConformalDecals/Text/DecalText.cs @@ -3,12 +3,16 @@ using System.Text.RegularExpressions; namespace ConformalDecals.Text { public class DecalText : IEquatable { + /// Raw text contents public string Text { get; } + /// Font asset used by this text snippet public DecalFont Font { get; } + /// Style used by this text snippet public DecalTextStyle Style { get; } + /// The text formatted with newlines for vertical text public string FormattedText { get { if (Style.Vertical) { diff --git a/Source/ConformalDecals/Text/DecalTextStyle.cs b/Source/ConformalDecals/Text/DecalTextStyle.cs index 528c995..ac55fc3 100644 --- a/Source/ConformalDecals/Text/DecalTextStyle.cs +++ b/Source/ConformalDecals/Text/DecalTextStyle.cs @@ -1,14 +1,15 @@ using System; using TMPro; using UnityEngine; + // ReSharper disable NonReadonlyMemberInGetHashCode namespace ConformalDecals.Text { public struct DecalTextStyle : IEquatable { private FontStyles _fontStyle; - private bool _vertical; - private float _lineSpacing; - private float _charSpacing; + private bool _vertical; + private float _lineSpacing; + private float _charSpacing; public FontStyles FontStyle { get => _fontStyle; @@ -61,7 +62,7 @@ namespace ConformalDecals.Text { get => _charSpacing; set => _charSpacing = value; } - + public DecalTextStyle(FontStyles fontStyle, bool vertical, float lineSpacing, float charSpacing) { _fontStyle = fontStyle; _vertical = vertical; @@ -70,7 +71,7 @@ namespace ConformalDecals.Text { } public bool Equals(DecalTextStyle other) { - return FontStyle == other.FontStyle && Vertical == other.Vertical && + return FontStyle == other.FontStyle && Vertical == other.Vertical && Mathf.Approximately(LineSpacing, other.LineSpacing) && Mathf.Approximately(CharSpacing, other.CharSpacing); } diff --git a/Source/ConformalDecals/Text/FontLoader.cs b/Source/ConformalDecals/Text/FontLoader.cs index 76351cb..ddb13e5 100644 --- a/Source/ConformalDecals/Text/FontLoader.cs +++ b/Source/ConformalDecals/Text/FontLoader.cs @@ -6,6 +6,7 @@ using UniLinq; using UnityEngine; namespace ConformalDecals.Text { + /// KSP database loader for KSPFont files which contain TextMeshPro font assets [DatabaseLoaderAttrib(new[] {"kspfont"})] public class FontLoader : DatabaseLoader { private const string FallbackName = "NotoSans-Regular SDF"; diff --git a/Source/ConformalDecals/Text/TextRenderOutput.cs b/Source/ConformalDecals/Text/TextRenderOutput.cs index 035bda5..210e14a 100644 --- a/Source/ConformalDecals/Text/TextRenderOutput.cs +++ b/Source/ConformalDecals/Text/TextRenderOutput.cs @@ -1,11 +1,15 @@ using UnityEngine; namespace ConformalDecals.Text { + /// Texture render output, used for cacheing and is the datastructure returned to the ModuleConformalText class public class TextRenderOutput { + /// Texture with the rendered text public Texture2D Texture { get; private set; } + /// The rectangle that the rendered text takes up within the texture public Rect Window { get; private set; } - + + /// The number of users for this render output. If 0, it can be discarded from the cache and the texture reused public int UserCount { get; set; } public TextRenderOutput(Texture2D texture, Rect window) { diff --git a/Source/ConformalDecals/Text/TextRenderer.cs b/Source/ConformalDecals/Text/TextRenderer.cs index 44d1d62..f7eb319 100644 --- a/Source/ConformalDecals/Text/TextRenderer.cs +++ b/Source/ConformalDecals/Text/TextRenderer.cs @@ -5,11 +5,21 @@ using UnityEngine; using UnityEngine.Events; namespace ConformalDecals.Text { + // TODO: Testing shows the job system is unnecessary, so remove job system code. + + /// Class handing text rendering. + /// Is a singleton referencing a single gameobject in the scene which contains the TextMeshPro component [KSPAddon(KSPAddon.Startup.Instantly, true)] public class TextRenderer : MonoBehaviour { - public const TextureFormat TextTextureFormat = TextureFormat.RG16; + /// Texture format used for returned textures. + /// Unfortunately due to how Unity textures work, this cannot be R8 or Alpha8, + /// so theres always a superfluous green channel using memory + public const TextureFormat TextTextureFormat = TextureFormat.RG16; + + /// Render Texture format used when rendering public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8; + /// The text renderer object within the scene which contains the TextMeshPro component used for rendering. public static TextRenderer Instance { get { if (!_instance._isSetup) { @@ -20,6 +30,7 @@ namespace ConformalDecals.Text { } } + /// Text Render unityevent, used with the job system to signal render completion [Serializable] public class TextRenderEvent : UnityEvent { } @@ -37,7 +48,7 @@ namespace ConformalDecals.Text { private static readonly Dictionary RenderCache = new Dictionary(); private static readonly Queue RenderJobs = new Queue(); - // Update text using the job queue + /// Update text using the job queue public static TextRenderJob UpdateText(DecalText oldText, DecalText newText, UnityAction renderFinishedCallback) { if (newText == null) throw new ArgumentNullException(nameof(newText)); @@ -46,14 +57,14 @@ namespace ConformalDecals.Text { return job; } - // Update text immediately without using job queue + /// Update text immediately without using job queue public static TextRenderOutput UpdateTextNow(DecalText oldText, DecalText newText) { if (newText == null) throw new ArgumentNullException(nameof(newText)); return Instance.RunJob(new TextRenderJob(oldText, newText, null), out _); } - // Unregister a user of a piece of text + /// Unregister a user of a piece of text public static void UnregisterText(DecalText text) { Debug.Log($"[ConformalDecals] Unregistering text '{text.Text}'"); if (RenderCache.TryGetValue(text, out var renderedText)) { @@ -75,6 +86,7 @@ namespace ConformalDecals.Text { DontDestroyOnLoad(gameObject); } + /// Setup this text renderer instance for rendering private void Setup() { if (_isSetup) return; @@ -89,7 +101,7 @@ namespace ConformalDecals.Text { _isSetup = true; } - // Run a text render job + /// Run a text render job private TextRenderOutput RunJob(TextRenderJob job, out bool renderNeeded) { if (!job.Needed) { renderNeeded = false; @@ -100,6 +112,7 @@ namespace ConformalDecals.Text { foreach (var cacheitem in RenderCache) { Debug.Log($"[ConformalDecals] Cache item: '{cacheitem.Key.Text}' with {cacheitem.Value.UserCount} users"); } + job.Start(); Texture2D texture = null; @@ -134,16 +147,16 @@ namespace ConformalDecals.Text { } renderOutput.UserCount++; - + job.Finish(renderOutput); return renderOutput; } - // Render a piece of text to a given texture + /// Render a piece of text to a given texture public TextRenderOutput RenderText(DecalText text, Texture2D texture) { if (text == null) throw new ArgumentNullException(nameof(text)); if (_tmp == null) throw new InvalidOperationException("TextMeshPro object not yet created."); - + // SETUP TMP OBJECT FOR RENDERING _tmp.text = text.FormattedText; _tmp.font = text.Font.FontAsset; @@ -171,13 +184,13 @@ namespace ConformalDecals.Text { // SETUP MATERIALS AND BOUNDS for (int i = 0; i < meshFilters.Length; i++) { var renderer = meshFilters[i].gameObject.GetComponent(); - + meshes[i] = meshFilters[i].mesh; if (i == 0) meshes[i] = _tmp.mesh; materials[i] = Instantiate(renderer.material); materials[i].shader = _blitShader; - + if (renderer == null) throw new FormatException($"Object {meshFilters[i].gameObject.name} has filter but no renderer"); if (meshes[i] == null) throw new FormatException($"Object {meshFilters[i].gameObject.name} has a null mesh"); diff --git a/Source/ConformalDecals/Util/TextureUtils.cs b/Source/ConformalDecals/Util/TextureUtils.cs deleted file mode 100644 index f835afe..0000000 --- a/Source/ConformalDecals/Util/TextureUtils.cs +++ /dev/null @@ -1,263 +0,0 @@ -using UnityEngine; - -namespace ConformalDecals.Util { - public static class TextureUtils { - public enum BlitMode { - Set, - Add, - } - - public static Color32 AddColor32(Color32 color1, Color32 color2) { - return new Color32((byte) (color1.r + color2.r), (byte) (color1.g + color2.g), (byte) (color1.b + color2.b), (byte) (color1.a + color2.a)); - } - - public static Color32 AddColor32Clamped(Color32 color1, Color32 color2) { - var r = color1.r + color2.r; - var g = color1.g + color2.g; - var b = color1.b + color2.b; - var a = color1.a + color2.a; - if (r > byte.MaxValue) r = byte.MaxValue; - if (g > byte.MaxValue) g = byte.MaxValue; - if (b > byte.MaxValue) b = byte.MaxValue; - if (a > byte.MaxValue) a = byte.MaxValue; - - return new Color32((byte) r, (byte) g, (byte) b, (byte) a); - } - - public static void ClearTexture(Color32[] colors, Color32 clearColor = default) { - for (var i = 0; i < colors.Length; i++) { - colors[i] = clearColor; - } - } - - public static void BlitRectAlpha( - Texture2D src, Color32[] srcColors, Vector2Int srcPos, - Texture2D dst, Color32[] dstColors, Vector2Int dstPos, - Vector2Int size, BlitMode mode) { - - ClipRect(src, ref srcPos, dst, ref dstPos, ref size); - - if (size.x <= 0 || size.y <= 0) return; - - int srcIndex = srcPos.x + srcPos.y * src.width; - int dstIndex = dstPos.x + dstPos.y * dst.width; - - for (int dy = size.y - 1; dy >= 0; dy--) { - - for (int dx = size.x - 1; dx >= 0; dx--) { - switch (mode) { - case BlitMode.Set: - dstColors[dstIndex + dx].a = srcColors[srcIndex + dx].a; - break; - case BlitMode.Add: - var s = srcColors[srcIndex + dx].a; - var d = dstColors[dstIndex + dx].a; - var sum = s + d; - if (sum > byte.MaxValue) sum = byte.MaxValue; - dstColors[dstIndex + dx].a = (byte) sum; - break; - } - } - - srcIndex += src.width; - dstIndex += dst.width; - } - } - - public static void BlitRect( - Texture2D src, Color32[] srcColors, Vector2Int srcPos, - Texture2D dst, Color32[] dstColors, Vector2Int dstPos, - Vector2Int size, BlitMode mode) { - - ClipRect(src, ref srcPos, dst, ref dstPos, ref size); - - if (size.x <= 0 || size.y <= 0) return; - - int srcIndex = srcPos.x + srcPos.y * src.width; - int dstIndex = dstPos.x + dstPos.y * dst.width; - - for (int dy = 0; dy < size.y; dy++) { - - for (int dx = 0; dx < size.x; dx++) { - switch (mode) { - case BlitMode.Set: - dstColors[dstIndex + dx] = srcColors[srcIndex + dx]; - break; - case BlitMode.Add: - dstColors[dstIndex + dx] = AddColor32Clamped(srcColors[srcIndex + dx], dstColors[dstIndex + dx]); - break; - } - } - - srcIndex += src.width; - dstIndex += dst.width; - } - } - - public static void BlitRectBilinearAlpha( - Texture2D src, Vector2Int srcPos, Vector2 srcSize, - Texture2D dst, Color32[] dstColors, Vector2Int dstPos, Vector2Int dstSize, - BlitMode mode) { - - var sizeRatio = dstSize / srcSize; - - ClipRect(src, ref srcPos, dst, ref dstPos, ref srcSize, ref dstSize); - - if (dstSize.x <= 0 || dstSize.y <= 0) return; - - var srcPixel = new Vector2(1.0f / src.width, 1.0f / src.height); - var srcStart = (srcPos * srcPixel) + (srcPixel / 2); - var srcStep = sizeRatio * srcPixel; - var srcY = srcStart.y; - - int dstIndex = dstPos.x + dstPos.y * dst.width; - for (int dy = 0; - dy < dstSize.y; - dy++) { - var srcX = srcStart.x; - - for (int dx = 0; dx < dstSize.x; dx++) { - switch (mode) { - case BlitMode.Set: - dstColors[dstIndex + dx].a = (byte) (src.GetPixelBilinear(srcX, srcY).a * byte.MaxValue); - break; - case BlitMode.Add: - var s = (byte) (src.GetPixelBilinear(srcX, srcY).a * byte.MaxValue); - var d = dstColors[dstIndex + dx].a; - var sum = s + d; - if (sum > byte.MaxValue) sum = byte.MaxValue; - dstColors[dstIndex + dx].a = (byte) sum; - break; - } - - srcX += srcStep.x; - } - - srcY += srcStep.y; - dstIndex += dst.width; - } - } - - public static void BlitRectBilinear( - Texture2D src, Vector2Int srcPos, Vector2 srcSize, - Texture2D dst, Color32[] dstColors, Vector2Int dstPos, Vector2Int dstSize, - BlitMode mode) { - - var sizeRatio = dstSize / srcSize; - - ClipRect(src, ref srcPos, dst, ref dstPos, ref srcSize, ref dstSize); - - if (dstSize.x <= 0 || dstSize.y <= 0) return; - - var srcPixel = new Vector2(1.0f / src.width, 1.0f / src.height); - var srcStart = (srcPos * srcPixel) + (srcPixel / 2); - var srcStep = sizeRatio * srcPixel; - var srcY = srcStart.y; - - int dstIndex = dstPos.x + dstPos.y * dst.width; - for (int dy = 0; - dy < dstSize.y; - dy++) { - var srcX = srcStart.x; - - for (int dx = 0; dx < dstSize.x; dx++) { - switch (mode) { - case BlitMode.Set: - dstColors[dstIndex + dx] = src.GetPixelBilinear(srcX, srcY); - break; - case BlitMode.Add: - dstColors[dstIndex + dx] = AddColor32Clamped(src.GetPixelBilinear(srcX, srcY), dstColors[dstIndex + dx]); - break; - } - - srcX += srcStep.x; - } - - srcY += srcStep.y; - dstIndex += dst.width; - } - } - - private static void ClipRect(Texture2D src, ref Vector2Int srcPos, Texture2D dst, ref Vector2Int dstPos, ref Vector2Int size) { - if (srcPos.x < 0) { - size.x += srcPos.x; - dstPos.x -= srcPos.x; - srcPos.x = 0; - } - - if (srcPos.y < 0) { - size.y += srcPos.y; - dstPos.y -= srcPos.y; - srcPos.y = 0; - } - - if (dstPos.x < 0) { - size.x += dstPos.x; - srcPos.x -= dstPos.x; - dstPos.x = 0; - } - - if (dstPos.y < 0) { - size.y += dstPos.y; - srcPos.y -= dstPos.y; - dstPos.y = 0; - } - - if (srcPos.x + size.x > src.width) size.x = src.width - srcPos.x; - if (srcPos.y + size.y > src.height) size.y = src.height - srcPos.y; - if (dstPos.x + size.x > dst.width) size.x = dst.width - srcPos.x; - if (dstPos.y + size.y > dst.height) size.y = dst.height - srcPos.y; - } - - private static void ClipRect(Texture2D src, ref Vector2Int srcPos, Texture2D dst, ref Vector2Int dstPos, ref Vector2 srcSize, ref Vector2Int dstSize) { - var sizeRatio = dstSize / srcSize; - if (srcPos.x < 0) { - dstSize.x += (int) (srcPos.x * sizeRatio.x); - dstPos.x -= (int) (srcPos.x * sizeRatio.x); - srcSize.x += srcPos.x; - srcPos.x = 0; - } - - if (srcPos.y < 0) { - dstSize.y += (int) (srcPos.y * sizeRatio.y); - dstPos.y -= (int) (srcPos.y * sizeRatio.y); - srcSize.y += srcPos.y; - srcPos.y = 0; - } - - if (dstPos.x < 0) { - srcSize.x += dstPos.x / sizeRatio.x; - srcPos.x -= (int) (dstPos.x / sizeRatio.x); - dstSize.x += dstPos.x; - dstPos.x = 0; - } - - if (dstPos.y < 0) { - srcSize.y += dstPos.y / sizeRatio.y; - srcPos.y -= (int) (dstPos.y / sizeRatio.y); - dstSize.y += dstPos.y; - dstPos.y = 0; - } - - if (srcPos.x + srcSize.x > src.width) { - srcSize.x = src.width - srcPos.x; - dstSize.x = (int) (srcSize.x * sizeRatio.x); - } - - if (srcPos.y + srcSize.y > src.height) { - srcSize.y = src.height - srcPos.y; - dstSize.y = (int) (srcSize.y * sizeRatio.y); - } - - if (dstPos.x + dstSize.x > dst.width) { - dstSize.x = dst.width - srcPos.x; - srcSize.x = (int) (dstSize.x / sizeRatio.x); - } - - if (dstPos.y + dstSize.y > dst.height) { - dstSize.y = dst.height - srcPos.y; - srcSize.y = (int) (dstSize.y / sizeRatio.y); - } - } - } -} \ No newline at end of file