diff --git a/Assets/Shaders/TMPBlit.shader b/Assets/Shaders/TextBlit.shader
similarity index 93%
rename from Assets/Shaders/TMPBlit.shader
rename to Assets/Shaders/TextBlit.shader
index 1ef6391..0352834 100644
--- a/Assets/Shaders/TMPBlit.shader
+++ b/Assets/Shaders/TextBlit.shader
@@ -1,4 +1,4 @@
-Shader "ConformalDecals/TMP_Blit"
+Shader "ConformalDecals/Text Blit"
{
Properties
{
@@ -13,7 +13,7 @@ Shader "ConformalDecals/TMP_Blit"
Pass
{
- Blend One One
+ BlendOp Max
CGPROGRAM
#pragma vertex vert
diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll
index e7466e5..1ac867a 100644
Binary files a/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ
diff --git a/GameData/ConformalDecals/Resources/conformaldecals.kspfont b/GameData/ConformalDecals/Resources/conformaldecals.kspfont
index 09a558e..e591a61 100644
--- a/GameData/ConformalDecals/Resources/conformaldecals.kspfont
+++ b/GameData/ConformalDecals/Resources/conformaldecals.kspfont
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:282c893d34ca9c703aee8c1af30efe3fcf9fb38d28cee5097a470ddcc2eaaf7a
-size 364831
+oid sha256:fe7efdec2d12a1bad8b4661cbb53b092d8082c2a704bb7c2cf18e5c9ab555184
+size 364850
diff --git a/GameData/ConformalDecals/Resources/conformaldecals.shab b/GameData/ConformalDecals/Resources/conformaldecals.shab
index ebcd050..3ee78d4 100644
Binary files a/GameData/ConformalDecals/Resources/conformaldecals.shab and b/GameData/ConformalDecals/Resources/conformaldecals.shab differ
diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj
index e1fea8a..f4c8b7a 100644
--- a/Source/ConformalDecals/ConformalDecals.csproj
+++ b/Source/ConformalDecals/ConformalDecals.csproj
@@ -88,6 +88,8 @@
+
+
diff --git a/Source/ConformalDecals/ModuleConformalText.cs b/Source/ConformalDecals/ModuleConformalText.cs
index 95e8bcd..a851536 100644
--- a/Source/ConformalDecals/ModuleConformalText.cs
+++ b/Source/ConformalDecals/ModuleConformalText.cs
@@ -1,20 +1,20 @@
using ConformalDecals.Text;
using ConformalDecals.UI;
-using TMPro;
using UnityEngine;
namespace ConformalDecals {
public class ModuleConformalText : ModuleConformalDecal {
[KSPField(isPersistant = true)] public string text = "Hello World!";
- [KSPField(isPersistant = true)] public string font = "Calibri SDF";
+ [KSPField(isPersistant = true)] public string fontName = "Calibri SDF";
[KSPField(isPersistant = true)] public int style;
[KSPField(isPersistant = true)] public bool vertical;
[KSPField(isPersistant = true)] public Color fillColor = Color.black;
[KSPField(isPersistant = true)] public Color outlineColor = Color.white;
[KSPField(isPersistant = true)] public float outlineWidth;
- private DecalText _text;
-
+ private DecalTextStyle _style;
+ private DecalFont _font;
+
private TextEntryController _textEntryController;
private ColorPickerController _fillColorPickerController;
private ColorPickerController _outlineColorPickerCOntroller;
@@ -22,21 +22,21 @@ namespace ConformalDecals {
public override void OnStart(StartState state) {
base.OnStart(state);
- var decalFont = DecalConfig.GetFont(font);
+ _font = DecalConfig.GetFont(fontName);
+ _style = new DecalTextStyle();
- _text = new DecalText {
- text = text,
- font = decalFont,
- style = (FontStyles) style,
- vertical = vertical,
- color = fillColor,
- outlineColor = outlineColor,
- outlineWidth = outlineWidth
- };
+ var decalText = new DecalText("Hello World!", _font, _style);
+
+ TextRenderer.Instance.RenderText(decalText, out var texture, out var window);
+ materialProperties.AddOrGetTextureProperty("_Decal", true).Texture = texture;
+ UpdateMaterials();
+ UpdateScale();
}
- public void OnTextUpdate(DecalText newText) {
- _text = newText;
+ public void OnTextUpdate(string newText, DecalFont newFont, DecalTextStyle newStyle) {
+ text = newText;
+ _font = newFont;
+ _style = newStyle;
}
public void OnFillColorUpdate(Color rgb, Util.ColorHSV hsv) {
@@ -50,7 +50,7 @@ namespace ConformalDecals {
[KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "#LOC_ConformalDecals_gui-select-flag")]
public void SetText() {
if (_textEntryController == null) {
- _textEntryController = TextEntryController.Create(_text, OnTextUpdate);
+ _textEntryController = TextEntryController.Create(text, _font, _style, OnTextUpdate);
}
}
diff --git a/Source/ConformalDecals/Text/DecalText.cs b/Source/ConformalDecals/Text/DecalText.cs
index aa59d21..f2f2792 100644
--- a/Source/ConformalDecals/Text/DecalText.cs
+++ b/Source/ConformalDecals/Text/DecalText.cs
@@ -1,16 +1,54 @@
using System;
-using TMPro;
-using UnityEngine;
+using System.Text.RegularExpressions;
namespace ConformalDecals.Text {
- public struct DecalText {
- public string text;
- public DecalFont font;
- public FontStyles style;
- public bool vertical;
-
- public Color color;
- public Color outlineColor;
- public float outlineWidth;
+ public class DecalText : IEquatable {
+ public string Text { get; }
+
+ public DecalFont Font { get; }
+
+ public DecalTextStyle Style { get; }
+
+ public string FormattedText {
+ get {
+ if (Style.Vertical) {
+ return Regex.Replace(Text, @"(.)", "$1\n");
+ }
+ else {
+ return Text;
+ }
+ }
+ }
+
+ public DecalText(string text, DecalFont font, DecalTextStyle style) {
+ Text = text;
+ Font = font;
+ Style = style;
+ }
+
+ public bool Equals(DecalText other) {
+ return other != null && (Text == other.Text && Equals(Font, other.Font) && Style.Equals(other.Style));
+ }
+
+ public override bool Equals(object obj) {
+ return obj is DecalText other && Equals(other);
+ }
+
+ public override int GetHashCode() {
+ unchecked {
+ var hashCode = (Text != null ? Text.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Font != null ? Font.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ Style.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(DecalText left, DecalText right) {
+ return left != null && left.Equals(right);
+ }
+
+ public static bool operator !=(DecalText left, DecalText right) {
+ return left != null && !left.Equals(right);
+ }
}
}
\ No newline at end of file
diff --git a/Source/ConformalDecals/Text/DecalTextStyle.cs b/Source/ConformalDecals/Text/DecalTextStyle.cs
new file mode 100644
index 0000000..459fed2
--- /dev/null
+++ b/Source/ConformalDecals/Text/DecalTextStyle.cs
@@ -0,0 +1,75 @@
+using System;
+using TMPro;
+using UnityEngine;
+
+namespace ConformalDecals.Text {
+ public struct DecalTextStyle : IEquatable {
+ public FontStyles FontStyle { get; set; }
+
+ public bool Bold {
+ get => (FontStyle & FontStyles.Bold) != 0;
+ set {
+ if (value) FontStyle |= FontStyles.Bold;
+ else FontStyle &= ~FontStyles.Bold;
+ }
+ }
+
+ public bool Italic {
+ get => (FontStyle & FontStyles.Italic) != 0;
+ set {
+ if (value) FontStyle |= FontStyles.Italic;
+ else FontStyle &= ~FontStyles.Italic;
+ }
+ }
+
+ public bool Underline {
+ get => (FontStyle & FontStyles.Underline) != 0;
+ set {
+ if (value) FontStyle |= FontStyles.Underline;
+ else FontStyle &= ~FontStyles.Underline;
+ }
+ }
+
+ public bool SmallCaps {
+ get => (FontStyle & FontStyles.SmallCaps) != 0;
+ set {
+ if (value) FontStyle |= FontStyles.SmallCaps;
+ else FontStyle &= ~FontStyles.SmallCaps;
+ }
+ }
+
+ public bool Vertical { get; set; }
+
+ public float LineSpacing { get; set; }
+
+ public float CharacterSpacing { get; set; }
+
+ public bool Equals(DecalTextStyle other) {
+ return FontStyle == other.FontStyle && Vertical == other.Vertical &&
+ Mathf.Approximately(LineSpacing, other.LineSpacing) &&
+ Mathf.Approximately(CharacterSpacing, other.CharacterSpacing);
+ }
+
+ public override bool Equals(object obj) {
+ return obj is DecalTextStyle other && Equals(other);
+ }
+
+ public override int GetHashCode() {
+ unchecked {
+ var hashCode = (int) FontStyle;
+ hashCode = (hashCode * 397) ^ Vertical.GetHashCode();
+ hashCode = (hashCode * 397) ^ LineSpacing.GetHashCode();
+ hashCode = (hashCode * 397) ^ CharacterSpacing.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(DecalTextStyle left, DecalTextStyle right) {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(DecalTextStyle left, DecalTextStyle right) {
+ return !left.Equals(right);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ConformalDecals/Text/RenderedText.cs b/Source/ConformalDecals/Text/RenderedText.cs
new file mode 100644
index 0000000..d00c293
--- /dev/null
+++ b/Source/ConformalDecals/Text/RenderedText.cs
@@ -0,0 +1,11 @@
+using UnityEngine;
+
+namespace ConformalDecals.Text {
+ public class RenderedText : ScriptableObject {
+ public Texture2D Texture { get; private set; }
+
+ public Rect Window { get; private set; }
+
+ public int UserCount { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Source/ConformalDecals/Text/TextRenderer.cs b/Source/ConformalDecals/Text/TextRenderer.cs
index 5d42a67..e717f65 100644
--- a/Source/ConformalDecals/Text/TextRenderer.cs
+++ b/Source/ConformalDecals/Text/TextRenderer.cs
@@ -1,11 +1,12 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
namespace ConformalDecals.Text {
- [KSPAddon(KSPAddon.Startup.FlightAndEditor, true)]
+ [KSPAddon(KSPAddon.Startup.Instantly, true)]
public class TextRenderer : MonoBehaviour {
public static TextRenderer Instance {
get {
@@ -17,22 +18,25 @@ namespace ConformalDecals.Text {
}
}
- public const TextureFormat TextTextureFormat = TextureFormat.Alpha8;
+ public const TextureFormat TextTextureFormat = TextureFormat.RG16;
public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8;
- private const string BlitShader = "ConformalDecals/TMP_Blit";
+ private const string BlitShader = "ConformalDecals/Text Blit";
private const int MaxTextureSize = 4096;
+ private const float FontSize = 100;
+ private const float PixelDensity = 5;
private static TextRenderer _instance;
private bool _isSetup;
private TextMeshPro _tmp;
- private GameObject _cameraObject;
- private Camera _camera;
private Material _blitMaterial;
+ private Dictionary _renderedTextures = new Dictionary();
+ private Texture2D _lastTexture; // to reduce the number of Texture2D objects created and destroyed, keep the last one on hand
+
private void Start() {
- if (_instance._isSetup) {
+ if (_instance != null) {
Debug.Log("[ConformalDecals] Duplicate TextRenderer created???");
}
@@ -41,28 +45,102 @@ namespace ConformalDecals.Text {
DontDestroyOnLoad(gameObject);
}
- public void Setup() {
+ private void Setup() {
if (_isSetup) return;
-
Debug.Log("[ConformalDecals] Setting Up TextRenderer Object");
_tmp = gameObject.AddComponent();
_tmp.renderer.enabled = false; // dont automatically render
- _cameraObject = new GameObject("ConformalDecals text camera");
- _cameraObject.transform.parent = transform;
- _cameraObject.transform.SetPositionAndRotation(Vector3.back, Quaternion.identity);
+ var shader = Shabby.Shabby.FindShader(BlitShader);
+ if (shader == null) Debug.LogError($"[ConformalDecals] could not find text blit shader named '{shader}'");
+ _blitMaterial = new Material(Shabby.Shabby.FindShader(BlitShader));
- _camera = _cameraObject.AddComponent();
- _camera.enabled = false; // dont automatically render
- _camera.orthographic = true;
- _camera.depthTextureMode = DepthTextureMode.None;
- _camera.nearClipPlane = 0.1f;
- _camera.farClipPlane = 2f;
_isSetup = true;
+ }
- _blitMaterial = new Material(Shabby.Shabby.FindShader(BlitShader));
+ public void RenderText(DecalText text, out Texture2D texture, out Rect window) {
+ // Setup TMP object for rendering
+ _tmp.text = text.FormattedText;
+ _tmp.font = text.Font.fontAsset;
+ _tmp.fontStyle = text.Style.FontStyle | text.Font.fontStyle;
+ _tmp.lineSpacing = text.Style.LineSpacing;
+ _tmp.characterSpacing = text.Style.CharacterSpacing;
+
+ _tmp.enableKerning = true;
+ _tmp.enableWordWrapping = false;
+ _tmp.overflowMode = TextOverflowModes.Overflow;
+ _tmp.alignment = TextAlignmentOptions.Center | TextAlignmentOptions.Baseline;
+ _tmp.fontSize = FontSize;
+
+ // Setup blit material
+ _blitMaterial.SetTexture(PropertyIDs._MainTex, text.Font.fontAsset.atlas);
+
+ // Generate Mesh
+ _tmp.ForceMeshUpdate();
+ var mesh = _tmp.mesh;
+ mesh.RecalculateBounds();
+ var bounds = mesh.bounds;
+
+ // Calculate Sizes
+ var size = bounds.size * PixelDensity;
+
+ var textureSize = new Vector2Int {
+ x = Mathf.NextPowerOfTwo((int) size.x),
+ y = Mathf.NextPowerOfTwo((int) size.y)
+ };
+
+ if (textureSize.x > MaxTextureSize) {
+ textureSize.x /= textureSize.x / MaxTextureSize;
+ textureSize.y /= textureSize.x / MaxTextureSize;
+ }
+
+ if (textureSize.y > MaxTextureSize) {
+ textureSize.x /= textureSize.y / MaxTextureSize;
+ textureSize.y /= textureSize.y / MaxTextureSize;
+ }
+
+ float sizeRatio = Mathf.Min(textureSize.x / size.x, textureSize.y, size.y);
+
+ window = new Rect {
+ size = size * sizeRatio,
+ center = (Vector2) textureSize / 2
+ };
+
+ // Get Texture
+ if (_lastTexture != null) {
+ texture = _lastTexture;
+ texture.Resize(textureSize.x, textureSize.y, TextTextureFormat, false);
+ _lastTexture = null;
+ }
+ else {
+ texture = new Texture2D(textureSize.x, textureSize.y, TextTextureFormat, false);
+ }
+
+ // Generate Projection Matrix
+ var halfSize = window.size / PixelDensity / 2;
+ var matrix = Matrix4x4.Ortho(bounds.center.x - halfSize.x, bounds.center.x + halfSize.x,
+ bounds.center.y - halfSize.y, bounds.center.y + halfSize.y, -1, 1);
+
+ // Get Rendertex
+ var renderTex = RenderTexture.GetTemporary(textureSize.x, textureSize.y, 0, TextRenderTextureFormat, RenderTextureReadWrite.Linear, 1);
+ renderTex.autoGenerateMips = false;
+
+ // Render
+ Graphics.SetRenderTarget(renderTex);
+ GL.PushMatrix();
+ GL.LoadProjectionMatrix(matrix);
+ _blitMaterial.SetPass(0);
+ Graphics.DrawMeshNow(mesh, Matrix4x4.identity);
+ GL.PopMatrix();
+
+ // Copy texture back into RAM
+ RenderTexture.active = renderTex;
+ texture.ReadPixels(new Rect(0, 0, textureSize.x, textureSize.y), 0, 0, false);
+ texture.Apply();
+
+ RenderTexture.ReleaseTemporary(renderTex);
}
}
}
\ No newline at end of file
diff --git a/Source/ConformalDecals/UI/TextEntryController.cs b/Source/ConformalDecals/UI/TextEntryController.cs
index 86dcbc7..0f06d18 100644
--- a/Source/ConformalDecals/UI/TextEntryController.cs
+++ b/Source/ConformalDecals/UI/TextEntryController.cs
@@ -8,46 +8,48 @@ using UnityEngine.UI;
namespace ConformalDecals.UI {
public class TextEntryController : MonoBehaviour {
[Serializable]
- public class TextUpdateEvent : UnityEvent { }
+ public class TextUpdateEvent : UnityEvent { }
- [SerializeField] public TextUpdateEvent onTextUpdate = new TextUpdateEvent();
+ [SerializeField] public TextUpdateEvent onValueChanged = new TextUpdateEvent();
[SerializeField] private Selectable _textBox;
[SerializeField] private Button _fontButton;
- [SerializeField] private Slider _outlineWidthSlider;
[SerializeField] private Toggle _boldButton;
[SerializeField] private Toggle _italicButton;
[SerializeField] private Toggle _underlineButton;
[SerializeField] private Toggle _smallCapsButton;
[SerializeField] private Toggle _verticalButton;
-
- private DecalText _decalText;
+
+ private string _text;
+ private DecalFont _font;
+ private DecalTextStyle _style;
+
private FontMenuController _fontMenu;
- public static TextEntryController Create(DecalText text, UnityAction textUpdateCallback) {
+ public static TextEntryController Create(string text, DecalFont font, DecalTextStyle style, UnityAction textUpdateCallback) {
+
var window = Instantiate(UILoader.TextEntryPrefab, MainCanvasUtil.MainCanvas.transform, true);
window.AddComponent();
MenuNavigation.SpawnMenuNavigation(window, Navigation.Mode.Automatic, true);
var controller = window.GetComponent();
- controller._decalText = text;
- controller.onTextUpdate.AddListener(textUpdateCallback);
+ controller._text = text;
+ controller.onValueChanged.AddListener(textUpdateCallback);
return controller;
}
private void Start() {
- ((TMP_InputField) _textBox).text = _decalText.text;
-
- _decalText.font.SetupSample(_fontButton.GetComponentInChildren());
+ ((TMP_InputField) _textBox).text = _text;
- _outlineWidthSlider.value = _decalText.outlineWidth;
- _boldButton.isOn = (_decalText.style & FontStyles.Bold) != 0;
- _italicButton.isOn = (_decalText.style & FontStyles.Italic) != 0;
- _underlineButton.isOn = (_decalText.style & FontStyles.Underline) != 0;
- _smallCapsButton.isOn = (_decalText.style & FontStyles.SmallCaps) != 0;
- _verticalButton.isOn = _decalText.vertical;
+ _font.SetupSample(_fontButton.GetComponentInChildren());
+
+ _boldButton.isOn = _style.Bold;
+ _italicButton.isOn = _style.Italic;
+ _underlineButton.isOn = _style.Underline;
+ _smallCapsButton.isOn = _style.SmallCaps;
+ _verticalButton.isOn = _style.Vertical;
}
@@ -56,93 +58,58 @@ namespace ConformalDecals.UI {
Destroy(gameObject);
}
- public void OnAnyUpdate() {
- onTextUpdate.Invoke(_decalText);
+ public void OnValueChanged() {
+ onValueChanged.Invoke(_text, _font, _style);
}
public void OnTextUpdate(string newText) {
- this._decalText.text = newText;
+ this._text = newText;
- OnAnyUpdate();
+ OnValueChanged();
}
public void OnFontMenu() {
- if (_fontMenu == null) _fontMenu = FontMenuController.Create(DecalConfig.Fonts, _decalText.font, OnFontUpdate);
+ if (_fontMenu == null) _fontMenu = FontMenuController.Create(DecalConfig.Fonts, _font, OnFontUpdate);
}
public void OnFontUpdate(DecalFont font) {
- _decalText.font = font;
+ _font = font;
font.SetupSample(_fontButton.GetComponentInChildren());
var textBox = ((TMP_InputField) _textBox);
- textBox.textComponent.fontStyle = _decalText.style | _decalText.font.fontStyle;
- textBox.fontAsset = _decalText.font.fontAsset;
-
- OnAnyUpdate();
- }
-
- public void OnColorMenu() { }
-
- public void OnColorUpdate(Color color) {
- _decalText.color = color;
- OnAnyUpdate();
- }
-
- public void OnOutlineColorMenu() { }
+ textBox.textComponent.fontStyle = _style.FontStyle | _font.fontStyle;
+ textBox.fontAsset = _font.fontAsset;
- public void OnOutlineColorUpdate(Color color) {
- _decalText.outlineColor = color;
- OnAnyUpdate();
- }
-
- public void OnOutlineUpdate(float value) {
- _decalText.outlineWidth = value;
- OnAnyUpdate();
+ OnValueChanged();
}
public void OnBoldUpdate(bool state) {
- if (state) _decalText.style |= FontStyles.Bold;
- else _decalText.style &= ~FontStyles.Bold;
-
- ((TMP_InputField) _textBox).textComponent.fontStyle = _decalText.style | _decalText.font.fontStyle;
-
- OnAnyUpdate();
+ _style.Bold = state;
+ OnValueChanged();
}
public void OnItalicUpdate(bool state) {
- if (state) _decalText.style |= FontStyles.Italic;
- else _decalText.style &= ~FontStyles.Italic;
-
- ((TMP_InputField) _textBox).textComponent.fontStyle = _decalText.style | _decalText.font.fontStyle;
-
- OnAnyUpdate();
+ _style.Italic = state;
+ OnValueChanged();
}
public void OnUnderlineUpdate(bool state) {
- if (state) _decalText.style |= FontStyles.Underline;
- else _decalText.style &= ~FontStyles.Underline;
-
- ((TMP_InputField) _textBox).textComponent.fontStyle = _decalText.style | _decalText.font.fontStyle;
-
- OnAnyUpdate();
+ _style.Underline = state;
+ OnValueChanged();
}
public void OnSmallCapsUpdate(bool state) {
- if (state) _decalText.style |= FontStyles.SmallCaps;
- else _decalText.style &= ~FontStyles.SmallCaps;
-
- ((TMP_InputField) _textBox).textComponent.fontStyle = _decalText.style | _decalText.font.fontStyle;
-
- OnAnyUpdate();
+ _style.SmallCaps = state;
+ OnValueChanged();
}
public void OnVerticalUpdate(bool state) {
- _decalText.vertical = state;
- OnAnyUpdate();
+ _style.Vertical = state;
+ OnValueChanged();
}
}
}
\ No newline at end of file