2020-06-15 22:39:05 +00:00
|
|
|
using System;
|
2020-07-24 21:39:35 +00:00
|
|
|
using System.Collections.Generic;
|
2020-06-15 22:39:05 +00:00
|
|
|
using TMPro;
|
|
|
|
using UnityEngine;
|
2020-07-26 03:01:41 +00:00
|
|
|
using UnityEngine.Events;
|
2020-06-19 08:01:46 +00:00
|
|
|
|
|
|
|
namespace ConformalDecals.Text {
|
2020-09-26 18:32:41 +00:00
|
|
|
[KSPAddon(KSPAddon.Startup.Instantly, true)]
|
2020-06-19 08:01:46 +00:00
|
|
|
public class TextRenderer : MonoBehaviour {
|
2020-07-26 03:01:41 +00:00
|
|
|
public const TextureFormat TextTextureFormat = TextureFormat.RG16;
|
|
|
|
public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8;
|
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
public static TextRenderer Instance {
|
|
|
|
get {
|
|
|
|
if (!_instance._isSetup) {
|
|
|
|
_instance.Setup();
|
|
|
|
}
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
return _instance;
|
|
|
|
}
|
2020-06-15 22:39:05 +00:00
|
|
|
}
|
|
|
|
|
2020-07-26 03:01:41 +00:00
|
|
|
[Serializable]
|
2020-07-27 02:32:58 +00:00
|
|
|
public class TextRenderEvent : UnityEvent<TextRenderOutput> { }
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-08-22 06:44:58 +00:00
|
|
|
private const string ShaderName = "ConformalDecals/Text Blit";
|
2020-06-19 08:01:46 +00:00
|
|
|
private const int MaxTextureSize = 4096;
|
2020-07-24 21:39:35 +00:00
|
|
|
private const float FontSize = 100;
|
|
|
|
private const float PixelDensity = 5;
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
private static TextRenderer _instance;
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
private bool _isSetup;
|
|
|
|
private TextMeshPro _tmp;
|
2020-08-17 20:50:36 +00:00
|
|
|
private Shader _blitShader;
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-07-27 02:32:58 +00:00
|
|
|
private static readonly Dictionary<DecalText, TextRenderOutput> RenderCache = new Dictionary<DecalText, TextRenderOutput>();
|
|
|
|
private static readonly Queue<TextRenderJob> RenderJobs = new Queue<TextRenderJob>();
|
2020-07-26 03:01:41 +00:00
|
|
|
|
2020-09-27 07:48:39 +00:00
|
|
|
// Update text using the job queue
|
2020-07-27 02:32:58 +00:00
|
|
|
public static TextRenderJob UpdateText(DecalText oldText, DecalText newText, UnityAction<TextRenderOutput> renderFinishedCallback) {
|
2020-07-27 23:16:15 +00:00
|
|
|
if (newText == null) throw new ArgumentNullException(nameof(newText));
|
|
|
|
|
2020-07-26 03:01:41 +00:00
|
|
|
var job = new TextRenderJob(oldText, newText, renderFinishedCallback);
|
2020-07-27 02:32:58 +00:00
|
|
|
RenderJobs.Enqueue(job);
|
2020-07-26 03:01:41 +00:00
|
|
|
return job;
|
|
|
|
}
|
2020-08-17 20:50:36 +00:00
|
|
|
|
2020-09-27 07:48:39 +00:00
|
|
|
// Update text immediately without using job queue
|
2020-07-27 23:16:15 +00:00
|
|
|
public static TextRenderOutput UpdateTextNow(DecalText oldText, DecalText newText) {
|
|
|
|
if (newText == null) throw new ArgumentNullException(nameof(newText));
|
2020-08-17 20:50:36 +00:00
|
|
|
|
2020-07-27 23:16:15 +00:00
|
|
|
return Instance.RunJob(new TextRenderJob(oldText, newText, null), out _);
|
|
|
|
}
|
2020-07-26 03:01:41 +00:00
|
|
|
|
2020-09-26 18:32:41 +00:00
|
|
|
// Unregister a user of a piece of text
|
2020-07-27 02:32:58 +00:00
|
|
|
public static void UnregisterText(DecalText text) {
|
2020-09-26 18:32:41 +00:00
|
|
|
Debug.Log($"[ConformalDecals] Unregistering text '{text.Text}'");
|
2020-07-27 02:32:58 +00:00
|
|
|
if (RenderCache.TryGetValue(text, out var renderedText)) {
|
2020-07-26 03:01:41 +00:00
|
|
|
renderedText.UserCount--;
|
|
|
|
if (renderedText.UserCount <= 0) {
|
2020-07-27 02:32:58 +00:00
|
|
|
RenderCache.Remove(text);
|
2020-07-26 03:01:41 +00:00
|
|
|
Destroy(renderedText.Texture);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-24 21:39:35 +00:00
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
private void Start() {
|
2020-07-24 21:39:35 +00:00
|
|
|
if (_instance != null) {
|
2020-06-19 08:01:46 +00:00
|
|
|
Debug.Log("[ConformalDecals] Duplicate TextRenderer created???");
|
|
|
|
}
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
Debug.Log("[ConformalDecals] Creating TextRenderer Object");
|
|
|
|
_instance = this;
|
|
|
|
DontDestroyOnLoad(gameObject);
|
|
|
|
}
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-07-24 21:39:35 +00:00
|
|
|
private void Setup() {
|
2020-06-19 08:01:46 +00:00
|
|
|
if (_isSetup) return;
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
Debug.Log("[ConformalDecals] Setting Up TextRenderer Object");
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
_tmp = gameObject.AddComponent<TextMeshPro>();
|
|
|
|
_tmp.renderer.enabled = false; // dont automatically render
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-08-22 06:44:58 +00:00
|
|
|
_blitShader = Shabby.Shabby.FindShader(ShaderName);
|
|
|
|
if (_blitShader == null) Debug.LogError($"[ConformalDecals] could not find text blit shader named '{ShaderName}'");
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-06-19 08:01:46 +00:00
|
|
|
_isSetup = true;
|
2020-07-24 21:39:35 +00:00
|
|
|
}
|
2020-06-15 22:39:05 +00:00
|
|
|
|
2020-09-27 07:48:39 +00:00
|
|
|
// Run a text render job
|
2020-07-27 23:16:15 +00:00
|
|
|
private TextRenderOutput RunJob(TextRenderJob job, out bool renderNeeded) {
|
|
|
|
if (!job.Needed) {
|
|
|
|
renderNeeded = false;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-09-26 18:32:41 +00:00
|
|
|
Debug.Log($"[ConformalDecals] Starting Text Rendering Job. queue depth = {RenderJobs.Count}, cache size = {RenderCache.Count}");
|
|
|
|
foreach (var cacheitem in RenderCache) {
|
|
|
|
Debug.Log($"[ConformalDecals] Cache item: '{cacheitem.Key.Text}' with {cacheitem.Value.UserCount} users");
|
|
|
|
}
|
2020-07-26 03:01:41 +00:00
|
|
|
job.Start();
|
2020-07-27 02:32:58 +00:00
|
|
|
|
2020-07-26 03:01:41 +00:00
|
|
|
Texture2D texture = null;
|
2020-07-27 02:32:58 +00:00
|
|
|
if (job.OldText != null && RenderCache.TryGetValue(job.OldText, out var oldRender)) {
|
2020-07-26 03:01:41 +00:00
|
|
|
// old output still exists
|
|
|
|
|
|
|
|
oldRender.UserCount--;
|
2020-07-27 02:32:58 +00:00
|
|
|
|
2020-07-26 03:01:41 +00:00
|
|
|
if (oldRender.UserCount <= 0) {
|
|
|
|
// this is the only usage of this output, so we are free to re-render into the texture
|
|
|
|
Debug.Log("Render output is not shared with other users, so reusing texture and removing cache slot");
|
2020-07-27 02:32:58 +00:00
|
|
|
|
2020-07-26 03:01:41 +00:00
|
|
|
texture = oldRender.Texture;
|
2020-07-27 02:32:58 +00:00
|
|
|
RenderCache.Remove(job.OldText);
|
2020-07-26 03:01:41 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// other things are using this render output, so decriment usercount, and we'll make a new entry instead
|
|
|
|
Debug.Log("Render output is shared with other users, so making new output");
|
|
|
|
}
|
|
|
|
}
|
2020-07-27 02:32:58 +00:00
|
|
|
|
2020-07-26 03:01:41 +00:00
|
|
|
// now that all old references are handled, begin rendering the new output
|
|
|
|
|
2020-09-27 07:48:39 +00:00
|
|
|
if (RenderCache.TryGetValue(job.NewText, out var renderOutput)) {
|
2020-07-27 23:16:15 +00:00
|
|
|
renderNeeded = false;
|
2020-07-26 03:01:41 +00:00
|
|
|
}
|
2020-09-27 07:48:39 +00:00
|
|
|
else {
|
|
|
|
renderNeeded = true;
|
2020-07-26 03:01:41 +00:00
|
|
|
|
2020-09-27 07:48:39 +00:00
|
|
|
renderOutput = RenderText(job.NewText, texture);
|
|
|
|
RenderCache.Add(job.NewText, renderOutput);
|
|
|
|
}
|
2020-07-27 02:32:58 +00:00
|
|
|
|
2020-09-27 07:48:39 +00:00
|
|
|
renderOutput.UserCount++;
|
|
|
|
|
|
|
|
job.Finish(renderOutput);
|
|
|
|
return renderOutput;
|
2020-07-26 03:01:41 +00:00
|
|
|
}
|
|
|
|
|
2020-09-27 07:48:39 +00:00
|
|
|
// Render a piece of text to a given texture
|
2020-07-27 02:32:58 +00:00
|
|
|
public TextRenderOutput RenderText(DecalText text, Texture2D texture) {
|
2020-08-17 20:50:36 +00:00
|
|
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
|
|
|
if (_tmp == null) throw new InvalidOperationException("TextMeshPro object not yet created.");
|
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// SETUP TMP OBJECT FOR RENDERING
|
2020-07-24 21:39:35 +00:00
|
|
|
_tmp.text = text.FormattedText;
|
2020-07-25 08:36:19 +00:00
|
|
|
_tmp.font = text.Font.FontAsset;
|
|
|
|
_tmp.fontStyle = text.Style.FontStyle | text.Font.FontStyle;
|
2020-07-24 21:39:35 +00:00
|
|
|
_tmp.lineSpacing = text.Style.LineSpacing;
|
2020-08-22 06:44:58 +00:00
|
|
|
_tmp.characterSpacing = text.Style.CharSpacing;
|
2020-07-24 21:39:35 +00:00
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
_tmp.extraPadding = true;
|
2020-07-24 21:39:35 +00:00
|
|
|
_tmp.enableKerning = true;
|
|
|
|
_tmp.enableWordWrapping = false;
|
|
|
|
_tmp.overflowMode = TextOverflowModes.Overflow;
|
2020-08-17 20:50:36 +00:00
|
|
|
_tmp.alignment = TextAlignmentOptions.Center;
|
2020-07-24 21:39:35 +00:00
|
|
|
_tmp.fontSize = FontSize;
|
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// GENERATE MESH
|
2020-09-26 05:03:59 +00:00
|
|
|
_tmp.ClearMesh(false);
|
2020-07-24 21:39:35 +00:00
|
|
|
_tmp.ForceMeshUpdate();
|
2020-08-17 20:50:36 +00:00
|
|
|
|
|
|
|
var meshFilters = gameObject.GetComponentsInChildren<MeshFilter>();
|
|
|
|
var meshes = new Mesh[meshFilters.Length];
|
|
|
|
var materials = new Material[meshFilters.Length];
|
|
|
|
|
|
|
|
var bounds = new Bounds();
|
|
|
|
|
|
|
|
// SETUP MATERIALS AND BOUNDS
|
|
|
|
for (int i = 0; i < meshFilters.Length; i++) {
|
|
|
|
var renderer = meshFilters[i].gameObject.GetComponent<MeshRenderer>();
|
|
|
|
|
|
|
|
meshes[i] = meshFilters[i].mesh;
|
2020-09-26 05:03:59 +00:00
|
|
|
if (i == 0) meshes[i] = _tmp.mesh;
|
2020-08-17 20:50:36 +00:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
bounds = meshes[i].bounds;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
bounds.Encapsulate(meshes[i].bounds);
|
|
|
|
}
|
|
|
|
}
|
2020-07-24 21:39:35 +00:00
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// CALCULATE SIZES
|
2020-07-24 21:39:35 +00:00
|
|
|
var size = bounds.size * PixelDensity;
|
|
|
|
var textureSize = new Vector2Int {
|
|
|
|
x = Mathf.NextPowerOfTwo((int) size.x),
|
|
|
|
y = Mathf.NextPowerOfTwo((int) size.y)
|
|
|
|
};
|
|
|
|
|
2020-08-17 20:50:36 +00:00
|
|
|
if (textureSize.x == 0 || textureSize.y == 0) {
|
|
|
|
Debug.LogWarning("[ConformalDecals] No text present or error in texture size calculation. Aborting.");
|
|
|
|
return new TextRenderOutput(Texture2D.blackTexture, Rect.zero);
|
|
|
|
}
|
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// make sure texture isnt too big, scale it down if it is
|
|
|
|
// this is just so you dont crash the game by pasting in the entire script of The Bee Movie
|
2020-07-24 21:39:35 +00:00
|
|
|
if (textureSize.x > MaxTextureSize) {
|
|
|
|
textureSize.y /= textureSize.x / MaxTextureSize;
|
2020-07-25 07:47:36 +00:00
|
|
|
textureSize.x = MaxTextureSize;
|
2020-07-24 21:39:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (textureSize.y > MaxTextureSize) {
|
|
|
|
textureSize.x /= textureSize.y / MaxTextureSize;
|
2020-07-25 07:47:36 +00:00
|
|
|
textureSize.y = MaxTextureSize;
|
2020-07-24 21:39:35 +00:00
|
|
|
}
|
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// scale up everything to fit the texture for maximum usage
|
2020-08-07 05:54:51 +00:00
|
|
|
float sizeRatio = Mathf.Min(textureSize.x / size.x, textureSize.y / size.y);
|
2020-07-24 21:39:35 +00:00
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// calculate where in the texture the used area actually is
|
2020-07-26 03:01:41 +00:00
|
|
|
var window = new Rect {
|
2020-07-24 21:39:35 +00:00
|
|
|
size = size * sizeRatio,
|
|
|
|
center = (Vector2) textureSize / 2
|
|
|
|
};
|
2020-08-17 20:50:36 +00:00
|
|
|
|
2020-07-26 03:01:41 +00:00
|
|
|
// SETUP TEXTURE
|
|
|
|
if (texture == null) {
|
2020-08-22 06:44:58 +00:00
|
|
|
texture = new Texture2D(textureSize.x, textureSize.y, TextTextureFormat, true);
|
2020-07-24 21:39:35 +00:00
|
|
|
}
|
2020-07-26 03:01:41 +00:00
|
|
|
else if (texture.width != textureSize.x || texture.height != textureSize.y || texture.format != TextTextureFormat) {
|
2020-08-22 06:44:58 +00:00
|
|
|
texture.Resize(textureSize.x, textureSize.y, TextTextureFormat, true);
|
2020-07-26 03:01:41 +00:00
|
|
|
}
|
2020-07-24 21:39:35 +00:00
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// GENERATE PROJECTION MATRIX
|
|
|
|
var halfSize = (Vector2) textureSize / PixelDensity / 2 / sizeRatio;
|
2020-07-24 21:39:35 +00:00
|
|
|
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);
|
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// GET RENDERTEX
|
2020-07-24 21:39:35 +00:00
|
|
|
var renderTex = RenderTexture.GetTemporary(textureSize.x, textureSize.y, 0, TextRenderTextureFormat, RenderTextureReadWrite.Linear, 1);
|
|
|
|
renderTex.autoGenerateMips = false;
|
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// RENDER
|
2020-07-24 21:39:35 +00:00
|
|
|
Graphics.SetRenderTarget(renderTex);
|
|
|
|
GL.PushMatrix();
|
|
|
|
GL.LoadProjectionMatrix(matrix);
|
2020-08-07 05:54:51 +00:00
|
|
|
GL.Clear(false, true, Color.black);
|
2020-08-17 20:50:36 +00:00
|
|
|
|
|
|
|
for (var i = 0; i < meshes.Length; i++) {
|
|
|
|
if (meshes[i].vertexCount >= 3) {
|
|
|
|
materials[i].SetPass(0);
|
|
|
|
Graphics.DrawMeshNow(meshes[i], Matrix4x4.identity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-24 21:39:35 +00:00
|
|
|
GL.PopMatrix();
|
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// COPY TEXTURE BACK INTO RAM
|
2020-07-24 21:39:35 +00:00
|
|
|
RenderTexture.active = renderTex;
|
2020-08-22 06:44:58 +00:00
|
|
|
texture.ReadPixels(new Rect(0, 0, textureSize.x, textureSize.y), 0, 0, true);
|
2020-07-24 21:39:35 +00:00
|
|
|
texture.Apply();
|
|
|
|
|
2020-07-25 07:47:36 +00:00
|
|
|
// RELEASE RENDERTEX
|
2020-07-24 21:39:35 +00:00
|
|
|
RenderTexture.ReleaseTemporary(renderTex);
|
2020-07-26 03:01:41 +00:00
|
|
|
|
2020-08-17 20:50:36 +00:00
|
|
|
// CLEAR SUBMESHES
|
|
|
|
for (int i = 0; i < transform.childCount; i++) {
|
|
|
|
Destroy(transform.GetChild(i).gameObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new TextRenderOutput(texture, window);
|
2020-06-19 08:01:46 +00:00
|
|
|
}
|
2020-06-15 22:39:05 +00:00
|
|
|
}
|
|
|
|
}
|