307 lines
9.3 KiB
C#
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);
|
|
}
|
|
} |