Chman / Typogenic

Signed-distance field text rendering for Unity
zlib License
193 stars 31 forks source link

Click support breaks with word wrap #11

Closed lostfictions closed 8 years ago

lostfictions commented 9 years ago

Trying this on Unity 5.1.2f1.

Steps to repro:

Let me know if you need me to fork and set up a ready-made repro case! It seems pretty easy-to-reproduce.

lostfictions commented 9 years ago

Finally had time to dig into this a bit more. Looks like the generated glyph bounds assume left-aligned text when word wrap is enabled, so center- or right-aligned text will cause GetGlyphIndexAtWorldPoint() to report bogus values. (Turning "Draw Glyph Bounds" on makes this a lot more obvious.)

EDIT: no, wait, there's something weirder going on -- even when text is left-aligned click support under word wrap reports weird values. @gnustoboz, @Chman, any idea on this?

gnustoboz commented 9 years ago

Hey @lostfictions, sorry, haven't looked at this project lately and didn't see this until you @ mentioned me, so thanks. I'll try to take a look at this soon, hopefully this weekend, and let you know what I find. It was originally developed under Unity 4.x so it wouldn't surprise me if something isn't behaving itself now.

lostfictions commented 9 years ago

Thanks! Took another look at it and it seems like there's several problems:

lostfictions commented 9 years ago

Okay, making progress on this. The main thing that's broken now is using word wrap in tandem with center- or right-alignment. I can sort of see how the mesh offsetting works for this, but I'm not sure why the bounding boxes aren't being generated with the correct values.

gnustoboz commented 9 years ago

Ugh. There's a lot more refactoring in store here than I was hoping for. I've only looked at the last commit here and haven't seen your branch so forgive me if I cover any ground you've already sorted out. The root problem here is that the word wrap code came first, then I came along and added the click logic, and they're not cooperating because the glyph bounds are calculated when a character is rendered, but the word wrap logic is going back and modifying the verts afterwards.

When word wrap is disabled, RebuildMesh() gets the entire line length at once, then if the alignment is center or right it offsets the cursorX accordingly then renders the whole line using BlitString(). The glyph bounds get calculated and stored in a call from BlitString(), so everything lines up regardless of the alignment.

When word wrap is enabled, RebuildMesh() renders one word at a time as if it were left aligned, while tracking the current line width and a list of the current verts in vertexPointers[]. When the word wrap boundary is crossed, it calls OffsetStringPosition() to go back and offset all of the current verts based on the line width and the alignment, then it clears the current vert array and starts again on the next line. There's nothing that goes back and adjusts the glyph bounds at that point, which is why they're still in the left-alignment position.

Right now the bounds are only being stored in the main glyph bounds list, so the trick is to add a way to modify them on each wrap. It could keep track of which bounds need to be adjusted and then go back and adjust just those on each wrap, or it could pre-calculate each line's length so it knows the offset before it starts rendering and the alignment would work the same way it does with word wrap disabled, or it could have a local array like vertexPointers[] to track the bounds as they're generated, offset them as needed, then add them to the main collection.

So hopefully that helps, because I don't know how soon I'd be able to look at this again but I'll help however I can.

gnustoboz commented 9 years ago

Ha, wow. Looks like @invicticide actually has a pre-calculate solution in place that just isn't being used for rendering.

If you find this in RebuildMesh():

string[] lines = Regex.Split(Text, @"\n");

and replace it with this:

string displayText;
if (WordWrap <= 0)
    displayText = Text;
else
    displayText = GetWrappedText(Text);

string[] lines = Regex.Split(displayText, @"\n");

...then that pre-calculates the wrap points and adds line breaks into the displayed string itself, so the non-wrap render logic can be used. I just started testing it but it looks like you can then totally remove all of the word wrap logic in RebuildMesh and use the non-wrapping display logic for everything since it will honor the line breaks the way it should. The version I'm running right now has all of the word wrap logic removed except for GetWrappedText(), plus I removed the OffsetStringPosition() method entirely, and so far it seems to be working.

Edit: noticed and corrected who actually added this method.

lostfictions commented 9 years ago

Thanks so much for digging into this! I'd taken a look and come to a similar conclusion that a bunch of further refactoring would be needed.

Definitely hadn't thought of using GetWrappedText() instead of trying to disentangle all the existing logic though -- that's a great idea! I'll give it a go as soon as I have a moment to try it.