Files
textdecalURP/Assets/ru.chikalin/textdecal/Scripts/TextMeshProDecal.cs
Kirill Chikalin a3072a3693 first commit
2024-11-16 13:20:07 +03:00

307 lines
9.3 KiB
C#

using System;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
namespace ru.chikalin.textdecal
{
[ExecuteAlways]
[RequireComponent(typeof(TMP_Text))]
[DefaultExecutionOrder(11)]
[AddComponentMenu("Mesh/TextMeshPro - Decal")]
[HelpURL("https://assetstore.unity.com/")] // todo
#if UNITY_2021_2_OR_NEWER
[Icon("Editor/Resources/Icons/sticker.png")] // todo
#endif
public class TextMeshProDecal : MonoBehaviour
{
private const int VolumetricVertexCount = 8;
private const int VolumetricTrianglesCount = 36;
private readonly int[] _volumetricTriangles =
{
0, 1, 2, //face front
2, 3, 0,
1, 5, 6, //face top
6, 2, 1,
3, 2, 6, //face right
6, 7, 3,
4, 5, 1, //face left
1, 0, 4,
4, 0, 3, //face back
3, 7, 4,
7, 6, 5, //face bottom
5, 4, 7
};
[Tooltip("Volumetric mesh depth")] public float depth = 1;
private Vector3 _depthVector;
private TMP_Text _text;
private Vector3[] _vertices;
private int[] _triangles;
private Color[] _colors;
private Vector2[] _uv;
private Vector2[] _uv2;
private Vector2[] _uv4;
private Vector2[] _uv5;
private Vector2[] _uv6;
private Vector2[] _uv7;
private Vector2[] _uv8;
private Mesh _volumetricMesh;
private Mesh VolumetricMesh
{
get
{
if (_volumetricMesh != null) return _volumetricMesh;
_volumetricMesh = new Mesh
{
hideFlags = HideFlags.HideAndDontSave
};
#if DEVELOPMENT_BUILD || UNITY_EDITOR
_volumetricMesh.name = "TextMeshPro Decal Mesh";
#endif
_volumetricMesh.MarkDynamic();
return _volumetricMesh;
}
}
private MeshFilter _meshFilter;
private MeshFilter MeshFilter
{
get
{
if (_meshFilter != null) return _meshFilter;
_meshFilter = GetComponent<MeshFilter>();
if (_meshFilter != null) return _meshFilter;
_meshFilter = gameObject.AddComponent<MeshFilter>();
_meshFilter.hideFlags = HideFlags.HideInInspector | HideFlags.HideAndDontSave;
return _meshFilter;
}
}
private void Awake()
{
_text = GetComponent<TMP_Text>();
}
private void OnEnable()
{
UpdateDepthVector();
_text.OnPreRenderText += OnPreRenderText;
TMPro_EventManager.TEXT_CHANGED_EVENT.Add(OnTextChangedEvent);
OnPreRenderText(_text.textInfo);
}
private void OnDisable()
{
TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(OnTextChangedEvent);
_text.OnPreRenderText -= OnPreRenderText;
_text.ForceMeshUpdate();
MeshFilter.sharedMesh = _text.mesh;
if (_volumetricMesh != null)
{
DestroyImmediate(_volumetricMesh);
}
}
private void OnTextChangedEvent(Object obj)
{
if (obj != _text || !string.IsNullOrEmpty(_text.text)) return;
// force update if text is empty
OnPreRenderText(_text.textInfo);
}
private void OnPreRenderText(TMP_TextInfo textInfo)
{
// todo remove
if (_text.isVolumetricText)
{
var textMesh = _text.mesh;
MeshFilter.sharedMesh = textMesh;
}
else
{
PrepareMeshData(textInfo);
var characterIndex = 0;
for (var i = 0; i < textInfo.characterCount; i++)
{
var characterInfo = textInfo.characterInfo[i];
if (!characterInfo.isVisible) continue;
var materialIndex = characterInfo.materialReferenceIndex;
var meshInfo = textInfo.meshInfo[materialIndex];
AddCharacter(characterInfo, meshInfo, characterIndex++);
}
UpdateMesh();
}
}
/// <summary>
/// Add volumetric mesh for a character
/// </summary>
/// <param name="charInfo">TextMesh character info</param>
/// <param name="meshInfo">TextMesh mesh info</param>
/// <param name="characterIndex">Character index</param>
private void AddCharacter(TMP_CharacterInfo charInfo, TMP_MeshInfo meshInfo, int characterIndex)
{
var volumetricVertexIndex = characterIndex * VolumetricVertexCount;
var vertexIndex = charInfo.vertexIndex;
/*
* copy TextMesh vertices
*/
var bl = meshInfo.vertices[vertexIndex + 0];
var tl = meshInfo.vertices[vertexIndex + 1];
var tr = meshInfo.vertices[vertexIndex + 2];
var br = meshInfo.vertices[vertexIndex + 3];
_vertices[volumetricVertexIndex + 0] = bl;
_vertices[volumetricVertexIndex + 1] = tl;
_vertices[volumetricVertexIndex + 2] = tr;
_vertices[volumetricVertexIndex + 3] = br;
/*
* Extrude face to make a cube
*/
_vertices[volumetricVertexIndex + 4] = bl + _depthVector;
_vertices[volumetricVertexIndex + 5] = tl + _depthVector;
_vertices[volumetricVertexIndex + 6] = tr + _depthVector;
_vertices[volumetricVertexIndex + 7] = br + _depthVector;
/*
* Fill volumetric triangles
*/
for (var i = 0; i < _volumetricTriangles.Length; i++)
{
_triangles[characterIndex * VolumetricTrianglesCount + i] = volumetricVertexIndex + _volumetricTriangles[i];
}
/*
* Copy TextMesh UVs
*/
_uv[volumetricVertexIndex + 0] = charInfo.vertex_BL.uv;
_uv[volumetricVertexIndex + 1] = charInfo.vertex_TL.uv;
_uv[volumetricVertexIndex + 2] = charInfo.vertex_TR.uv;
_uv[volumetricVertexIndex + 3] = charInfo.vertex_BR.uv;
_uv[volumetricVertexIndex + 4] = charInfo.vertex_BL.uv;
_uv[volumetricVertexIndex + 5] = charInfo.vertex_TL.uv;
_uv[volumetricVertexIndex + 6] = charInfo.vertex_TR.uv;
_uv[volumetricVertexIndex + 7] = charInfo.vertex_BR.uv;
_uv2[volumetricVertexIndex + 0] = charInfo.vertex_BL.uv2;
_uv2[volumetricVertexIndex + 1] = charInfo.vertex_TL.uv2;
_uv2[volumetricVertexIndex + 2] = charInfo.vertex_TR.uv2;
_uv2[volumetricVertexIndex + 3] = charInfo.vertex_BR.uv2;
_uv2[volumetricVertexIndex + 4] = charInfo.vertex_BL.uv2;
_uv2[volumetricVertexIndex + 5] = charInfo.vertex_TL.uv2;
_uv2[volumetricVertexIndex + 6] = charInfo.vertex_TR.uv2;
_uv2[volumetricVertexIndex + 7] = charInfo.vertex_BR.uv2;
/*
* Additional mesh data for decal shader
*/
// character bottom left vertex position
var uv4 = new Vector2(bl.x, bl.y);
// character bottom left UV
var uv5 = charInfo.vertex_BL.uv;
// character 3D size
var width = Vector3.Distance(br, bl);
var height = Vector3.Distance(tr, br);
var uv6 = new Vector2(width, height);
// character UV size
var uvWidth = Vector2.Distance(charInfo.vertex_BR.uv, charInfo.vertex_BL.uv);
var uvHeight = Vector2.Distance(charInfo.vertex_TR.uv, charInfo.vertex_BR.uv);
var uv7 = new Vector2(uvWidth, uvHeight);
// character 3D Z-rotation
var angle = Vector3.SignedAngle(Vector3.up, tl - bl, Vector3.forward);
var uv8 = Mathf.Deg2Rad * angle;
for (var i = 0; i < VolumetricVertexCount; i++)
{
_uv4[volumetricVertexIndex + i] = uv4;
_uv5[volumetricVertexIndex + i] = uv5;
_uv6[volumetricVertexIndex + i] = uv6;
_uv7[volumetricVertexIndex + i] = uv7;
_colors[volumetricVertexIndex + i] = charInfo.color;
_uv8[volumetricVertexIndex + i].x = uv8;
}
}
private void UpdateMesh()
{
var mesh = VolumetricMesh;
mesh.Clear();
mesh.vertices = _vertices;
mesh.triangles = _triangles;
mesh.uv = _uv;
mesh.uv2 = _uv2;
mesh.colors = _colors;
mesh.uv4 = _uv4;
mesh.uv5 = _uv5;
mesh.uv6 = _uv6;
mesh.uv7 = _uv7;
mesh.uv8 = _uv8;
if (MeshFilter.sharedMesh != mesh)
{
MeshFilter.sharedMesh = mesh;
}
mesh.RecalculateBounds();
mesh.MarkModified();
}
private void PrepareMeshData(TMP_TextInfo textInfo)
{
var size = textInfo.characterInfo.Length;
var vertexSize = size * VolumetricVertexCount;
UpdateArray(ref _vertices, vertexSize);
UpdateArray(ref _triangles, size * VolumetricTrianglesCount);
UpdateArray(ref _colors, vertexSize);
UpdateArray(ref _uv, vertexSize);
UpdateArray(ref _uv2, vertexSize);
UpdateArray(ref _uv4, vertexSize);
UpdateArray(ref _uv5, vertexSize);
UpdateArray(ref _uv6, vertexSize);
UpdateArray(ref _uv7, vertexSize);
UpdateArray(ref _uv8, vertexSize);
}
private static void UpdateArray<T>(ref T[] array, int size)
{
if (array == null)
{
array = new T[size];
return;
}
if (array.Length < size)
{
Array.Resize(ref array, size);
}
Array.Clear(array, 0, array.Length);
}
#if UNITY_EDITOR
private void OnValidate()
{
if (_text != null)
{
_text.ForceMeshUpdate();
}
UpdateDepthVector();
}
#endif
private void UpdateDepthVector() => _depthVector = new Vector3(0, 0, depth);
}
}