first commit
This commit is contained in:
307
Assets/ru.chikalin/textdecal/Scripts/TextMeshProDecal.cs
Normal file
307
Assets/ru.chikalin/textdecal/Scripts/TextMeshProDecal.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user