using System.Linq; using TMPro; using Unity.Mathematics; using UnityEngine; using UnityEngine.Splines; namespace space.chikalin.textdecal { /// /// /// Should execute after space.chikalin.textdecal.TextMeshProDecal script /// [ExecuteAlways] [DisallowMultipleComponent] [DefaultExecutionOrder(-90)] [AddComponentMenu("Mesh/TextMeshPro - Spline")] #if UNITY_2021_2_OR_NEWER [Icon("Packages/com.unity.splines/Editor/Resources/Icons/SplineComponent.png")] #endif [RequireComponent(typeof(SplineContainer), typeof(TMP_Text))] public class TextMeshProSpline : MonoBehaviour { private TMP_Text _text; private SplineContainer _spline; private Spline DefaultSpline(float3 start, float3 end) { var spline = new Spline { { start, TangentMode.Linear }, { end, TangentMode.Linear } }; return spline; } private void Awake() { _text = GetComponent(); _spline = GetComponent(); } private void OnEnable() { _text.OnPreRenderText += OnPreRenderText; } private void OnDisable() { _text.OnPreRenderText -= OnPreRenderText; _text.ClearMesh(); _text.mesh.RecalculateBounds(); _text.ForceMeshUpdate(); } private void OnPreRenderText(TMP_TextInfo textInfo) { FitSpline(textInfo); } private void FitSpline(TMP_TextInfo textInfo) { FixSpline(); var splineLength = _spline.CalculateLength(); if (splineLength <= 0) return; for (var i = 0; i < textInfo.characterCount; i++) { if (!textInfo.characterInfo[i].isVisible) continue; var vertexIndex = textInfo.characterInfo[i].vertexIndex; var materialIndex = textInfo.characterInfo[i].materialReferenceIndex; var vertices = textInfo.meshInfo[materialIndex].vertices; var bl = vertices[vertexIndex + 0]; var tr = vertices[vertexIndex + 2]; var charCenter = bl + (tr - bl) / 2; Evaluate(splineLength, charCenter.x, out var position, out var xAxis, out var yAxis); xAxis = transform.InverseTransformDirection(math.normalize(xAxis)); yAxis = transform.InverseTransformDirection(math.normalize(yAxis)); position = transform.InverseTransformPoint(position); var newCenter = position + yAxis * charCenter.y; for (var v = 0; v < 4; v++) { var d = vertices[vertexIndex + v] - charCenter; vertices[vertexIndex + v] = newCenter + xAxis * d.x + yAxis * d.y; } } } private void FixSpline() { var textRectTransform = _text.rectTransform; if (_spline.Spline.Count >= 2) return; var rect = textRectTransform.rect; var position = textRectTransform.position; var start = new float3(rect.xMin, rect.yMin, position.z); var end = new float3(rect.xMax, rect.yMax, position.z); _spline.Spline = DefaultSpline(start, end); } private void Evaluate(float length, float xPos, out float3 position, out float3 xAxis, out float3 yAxis) { var s = (xPos / length) * transform.localScale.x + .5f; // centered _spline.Evaluate(math.clamp(s, 0.01f, .99f), out position, out xAxis, out yAxis); } private Quaternion ReflectRotation(Quaternion source, Vector3 normal) { return Quaternion.LookRotation(Vector3.Reflect(source * Vector3.forward, normal), Vector3.Reflect(source * Vector3.up, normal)); } #if UNITY_EDITOR [ExecuteAlways] [ContextMenu("Mirror")] public void Mirror() { var rectTransform = _text.rectTransform; rectTransform.GetPositionAndRotation(out var position, out var rotation); var max = position + _text.transform.right * rectTransform.rect.width * rectTransform.localScale.x; max.x = -max.x; var angles = rotation.eulerAngles; rotation = Quaternion.Euler(angles.x, 180 - (angles.y - 180), -angles.z); rectTransform.SetPositionAndRotation(max, rotation); var spline = _spline.Splines[0]; var array = spline.Knots.ToArray(); var lastX = array[^1].Position.x; var r = array.Length - 1; for (int i = 0; i < array.Length; i++) { var knot = array[i]; knot.Position.x = -knot.Position.x + lastX; spline.SetKnot(r - i, knot); } } #endif #if UNITY_EDITOR private void OnValidate() { ForceUpdate(); } #endif [ExecuteAlways] public void ForceUpdate() { if (_text != null) { _text.ForceMeshUpdate(); } } } }