TextMeshPro characterInfo.vertexIndex problem/bug?

Hi everybody,

I’ve been running into a problem with TextMeshPro that I cannot solve and would love some insight on why this is happening / how to fix it.
I’m aiming to start a Coroutine in order to reveal letters of a given word, while having each letter’s vertex position animated eventually. In order to reveal each letter, I’m incrementing the “maxVisibleCharacters” property of the TMP component, which works fine. However, when I’m then trying to access the vertexIndex of the current letter, a lot of times characterInfo.vertexInfo returns 0, instead of the expected value).
(In my opinion a word with 4 letters i.e. ‘word’ should have the vertex indeces 0, 4, 8, 12)

The IEnumerator RevealNewCharacter() method is being called from the Update() method in one of my game objects, please have a look at my code:

IEnumerator RevealNewCharacter()
    {
        // This adds a new letter to an existing string, iterates over the vertices and aninmates their positions
        textMeshTranslation = textTranslationTmp3D.GetComponent<TMP_Text>();
        charactersVisibleTranslation++;
        textMeshTranslation.maxVisibleCharacters = charactersVisibleTranslation;
        Debug.Log("AddCharacterFlyIn() - maxVisibleCharacters: " + textMeshTranslation.maxVisibleCharacters.ToString());

        textMeshTranslation.ForceMeshUpdate();
        meshTranslation = textMeshTranslation.mesh;
        verticesTranslation = meshTranslation.vertices;
        tinfoTranslation = textMeshTranslation.textInfo;

        // now store vertex positions for current character to reveal
        Debug.Log("AddCharacterFlyIn() - character count: " + tinfoTranslation.characterCount);
        TMP_CharacterInfo cInfoNext = tinfoTranslation.characterInfo[charactersVisibleTranslation - 1];

        // skipping over " " space characters
        string nextChar = cInfoNext.character.ToString();
        if (nextChar == " ")
        {
            animOnTranslation = false;
            yield break;
        }

        // each vertex square has four vertices in TMP, the vertexIndex is the first vertex of current letter
        int charIdxNext = cInfoNext.vertexIndex;

        // this is a test to debug vertex info problem
        Debug.Log("AddCharacterFlyIn() - Next char: " + cInfoNext.character.ToString() + " vertex Index: " + charIdxNext.ToString());
        for (int i = 0; i < tinfoTranslation.characterCount; i++)
        {
            TMP_CharacterInfo cInfo = tinfoTranslation.characterInfo[i];
            Debug.Log(">>> cur char: " + cInfo.character.ToString() + " vertex index: " + cInfo.vertexIndex);
        }

        // now add to lettersValidated list
        listLettersValidated.Add(nextChar);
        //PrintLetterList("listLettersValidated", listLettersValidated);

        // listLettersRemaining is being set initially by SetToTranslate() method
        // since we're validating the letter and assume it's sitting at position 0 of lettersRemaining list, we can remove it from the latter
        if (listLettersRemaining.Count > 0)
        {
            int idxRemaining = listLettersRemaining.IndexOf(nextChar);
            listLettersRemaining.RemoveAt(idxRemaining);
        }
        // we validated the letter, so we can remove it out of the scene
        if (listLettersInScene.Count > 0)
        {
            int idxInScene = listLettersInScene.IndexOf(nextChar);
            listLettersInScene.RemoveAt(idxInScene);
        }

        //PrintLetterList("listLettersRemaining", listLettersRemaining);
        //PrintLetterList("listLettersInScene", listLettersInScene);

        // Display last character of word a little longer before switching to new word
        if (textMeshTranslation.text.Length == textMeshTranslation.maxVisibleCharacters)
        {
            //Debug.Log("Word is complete");
            yield return new WaitForSeconds(0.5f);
            MarkTranslationComplete();
        }
       
        // skipping the animation for debugging
        animOnTranslation = false;
        yield return null;
    }

While I cannot post my entire project, because it’s too complex in size and confidential, I would appreciate some pointers as to why this could be happening.
Here’s a screenshot of the console, that logs my printing statements:

Thanks a lot for your help!
Cheers,

Manu

Is this consistent with how TMPro makes characters? You can look for yourself because all the TMPro source is right there in the package.

Hi Kurt,

Thanks for your reply. I haven’t looked into any source code, but all basic tests that I run confirm my theory.
If you put this simple code on a TextMeshPro 3D object, you’ll get the following prints in the console:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class testScriptTmp : MonoBehaviour
{
    TMP_Text tmp;
    TMP_TextInfo tmpInfo;

    // Start is called before the first frame update
    void Start()
    {
        tmp = gameObject.GetComponent<TMP_Text>();
        tmp.text = "Example";
        tmpInfo = tmp.textInfo;
        tmp.ForceMeshUpdate();

        Debug.Log("Current Word: " + tmp.text);
        for (int i = 0; i < tmpInfo.characterCount; i++)
        {
            TMP_CharacterInfo cInfo = tmpInfo.characterInfo[i];
            Debug.Log(">>> cur char: " + cInfo.character.ToString() + " vertex index: " + cInfo.vertexIndex);
        }
    }
}

7297879--883315--upload_2021-7-4_13-53-11.png

Here the vertex indeces are behaving as expected.
In my much more complex code, I’m switching out the words on certain conditions, and resetting the “maxVisibleCharacters” property back to 0, then using the “ForceMeshUpdate()” to update the TMP component, so potentially that would mess things up? Would really love to understand how to fix this!

You’re seeing something get messed up, so I am gonna answer “Sure!”

TMPro is a black box that you luckily have the source code to. Go inspect it, see if it does dynamically rearrange stuff.

At a bare minimum, make something visual onscreen that shows you onscreen where each vertex is as you change letters in the string, like visualize the vertices in their place in space, and what index each one is. I think you can use Handles.Label() to cheese those numbers onscreen and test your theories.

https://docs.unity3d.com/ScriptReference/Handles.Label.html

I have to say that I feel a little overwhelmed when looking into the source code.
While I managed to find the TMP_CharacterInfo script, it doesn’t show me how those properties such as “vertexIndex” are being set.

In this thread here Problem setting individual characters alpha - bug?
Stephan_B states that a character’s vertexIndex is 0 if it is not visible (isVisible = false), however that theory doesn’t hold up in my tests:

I also experimented with add those 2 lines before I iterate over the vertices, that didn’t help much either:

        textMeshTranslation.ForceMeshUpdate();
        textMeshTranslation.UpdateVertexData();

Quick update, I’m not one to give up easily, but soon I’ll be at my wits end :slight_smile:
If I completely disable playing with the “maxVisibleCharacters” property, the problem remains. In which case simply updating the TMP_Text.text property would mean that the vertex information becomes incorrect.
I assume I must be missing an important step to let TMP know that something has changed.
7298035--883327--upload_2021-7-4_15-30-56.png

Ok, so I do believe that I’ve found a bug.
If you look closely at the previous logs that I posted, you can see that the vertexIndex is always 0 for special characters, such as ö, ü, ß, ä, etc.
If I iterate over a word with standard letters, everything works as expected:
7298116--883345--upload_2021-7-4_16-17-34.png

Can anybody confirm that for me?
Since I’ve never submitted a bug report to Unity, what’s the standard procedure for this?

I have also noticed this issue on the newest version of TextMeshPro 3.0.6 and Unity 2020.3.7f1

Certain characters will cause the VertexIndex within TMP_CharacterInfo to return 0 and sometimes even reset the vertex count from that point on. For my use case I’m using this VertexIndex for text animations.

This is happening when all the characters are visible.

I haven’t really used TextMeshPro that much so I never ran into that issue. However it may be related to any unicode characters that require more than a single byte in UTF8 encoding. So everything besides ASCII characters may be affected. This is in general an issue because if you have multiple umlaut or in general non-ASCII characters in a string, there is no one-to-one relationship between “char” index in a string and the actual “character”.

So it’s possible that TMP may not decode the string properly if it contains unicode characters (anything outside the ASCII range). So yes, I would call it a bug. Maybe someone can create an example project that verifies this and file a bug report. Currently I don’t really use Unity :slight_smile:

For anyone stuck on this, try changing your font and see what happens.

In my case the text was showing correctly when using the text as-is, but when I went to modify vertex data it would seemingly not modify some characters on account of shared vertexIndex values as in the screenshots above. I traced the vertexIndex back to index_X4 in TMP_Text.FillCharacterVertexBuffers(), which is pulled from the material. I’m not totally sure how this links together yet, but the key takeaway is that when I went looking for the “skipped” characters in the font asset they weren’t there. I’m guessing that somehow messes with TextMeshPro and it just reuses the last index it had in memory.

Wish I didn’t have to spend a night figuring this all out, but if it helps someone else I’ll be happy.