Distance Field Fonts for jme3

Hi,

With regards to #359 “Suggestion: Support distance field based text rendering” Suggestion: Support distance field based text rendering · Issue #359 · jMonkeyEngine/jmonkeyengine · GitHub

Here is a simple implementation that builds upon the existing BitmapText:

  • As extra feature, it allows to set outline for the text.
  • For now, I have created a new repo. See, if you would like it as PR to master.
  • Currently, the material DFont.j3md is a copy of Unshaded, modified. Unneeded functionality can be removed from them.
  • DText extends BitmapText. If you can come up with a better name, feel free to suggest.
  • Atm, it uses a bit of reflection since BitmapText has private fields, which can be removed and made into protected onces once PR is ready.
  • DFontLoader is a copy of BitmapFontLoader, with few modification. BitmapFontLoader used textures with no mipmaps. I enabled mipmaps in DFontLoader. Do you have an opinion on this one?
  • BitmapFontLoader ignores “padding” in .fnt files, left and right padding was included in DFontLoader
  • DFontLoader: extension atm: “.dfnt”
  • Distance field bitmap can be created by Heiro (Hiero · libgdx/libgdx Wiki · GitHub) , guide here(Distance field fonts · libgdx/libgdx Wiki · GitHub)
  • TestCase: jme3DFont/TestDFont.java at master · TehLeo/jme3DFont · GitHub

And lastly feel free to try it and give a like if you like it :slight_smile: .

8 Likes

How did you handle antialiasing? I gave SDF text rendering a go around Christmas time, but I never got sufficient quality for small text to consider it as an acceptable alternative to bitmapped text. Antialiasing is theoretically easy, but I was not able to find a set of parameters that gave acceptable results without turning significant portion of the glyphs partially transparent.

1 Like

Nice! I’ve seen other people do this, but not as nicely integrated as your code.

Bookmarked this for later integration into the code I’m working atm. Thanks! :slight_smile:

1 Like

I simply followed the guide (Distance field fonts · libgdx/libgdx Wiki · GitHub).
In the end what it boils down to is:

  • The bitmap texture
  • smoothstep in fragment shader with well configuted parameters

Btw, when creating your font with Hiero, follow the guide, if you use different Spread, set your DText.Spread to the same number. Default is 4.0.

Ideally, the spread value could be put into the .dfnt file and then read.

2 Likes

That’s not too difficult to make - in the BitmapFontLoader just react to a token named “spread”.
Simply do a Float.valueOf(
[https://github.com/TehLeo/jme3DFont/blob/master/src/com/jme3/font/plugins/DFontLoader.java]
Then you would add that value to a DFont as a attribute.
But currently you use BitmapFont, which is okay too.
In order to make things better, you would just inherit DFont extends BitmapFont.
Also, the AssetLoader would get the name DFontLoader.

Problem is, that you would need to write these .dfnt files with that "spread=4.0" in it.
It’s not a big deal for me, since I’ve done similar things before.
Maybe I will add such functionality to your code later this week.
But atm I’m very busy, writing jME-related code too.
Idea:
You help me and I help you - later this week I will have my first github hosted project and may have some questions for this. But I can show you how to solve that thing from ^- up there. :slight_smile:

@Ogli Yes, exactly as you say. It’s not a technical issue.

Yes, this is exactly what I had in mind. But now that I think about it I can add it anyway. If spread is missing, I can use logger to print warning and use default value eg. 4.0 .

I’m fine with making the changes, your feedback is helpful enough. :slight_smile:

Regarding a PR to master:

  • I would like to hear reply from core team whether to make one. :slightly_smiling_face:
  • And possibly any suggestions/changes, features.
  • eg. I’d like to modify BitmapText fields from private to protected, etc
  • Would core team prefer DFontLoader or incorporate it into existing BitmapFontLoader, etc…
1 Like

That’d be great. Not a core dev though, but I’ve had the same issue with BitmapText / BitmapFont.

Protected methods to set/get them? maybe.

protected fields? probably not. Protected fields are generally an anti-pattern. JME has used this in the past for convenience and it often comes back to haunt us.

My question: what benefit was gained extending bitmap text to turn it into something that isn’t bitmap text anymore? Why couldn’t a separate class have worked?

…I think the answers to those questions may lead us to a very different destination.

Sure protected methods are fine too.

95% of the functionality is still done by BitmapText. Since after all Distance Field Text stores distance field of letters in a bitmap underneath,
So why not extend BitmapText.

BitmapText also uses many classes such as (BitmapTextPage, Letters, StringBlock) which are not public. (Thus, it would also require copying those to not extend BitmapText.)

I’m not picking on your solution. I’m just trying to understand why… your explanation is pretty good.

The thing is that the existing bitmap text sucks pretty badly. I want to move some functionality into the BitmapFont because it bugs me that there are two different loops that calculate where letters should be and they are both very different. If you fix a bug in one place then you have to fix it again a completely different way somewhere else. For example, if we want to fix the padding problem (which gets me with some of my Mythruna fonts) then we will have to change two places and the changes won’t be the same.

To me, BitmapFont should provide some kind of glyph iterator that can be used to calculate size or create letter quads, etc…

The letter quad linked list is kind of also not great… I don’t see a reason for it and it makes the code a lot more complicated.

It could be that as part of these other refactorings that we centralize other things that different font implementations might want. BitmapText could become an implementation of a new Text interface that has the common set/getText/alpha/color, etc… on it. Then we could have another implementation for TTF one for distance fields, etc… Using or not using the other shared classes as required.

The only thing that has stopped me in the past is how fragile this code is and how few test cases we have. If we could develop a small suite of some really good font test cases, maybe with side-by-side views of the way swing would render the real font with the way JME is rendering it, different alignments, etc… then 1) we could see all of the current bugs (which there definitely are some), and 2) it would be really easy to write a new jme.font package that shed itself of all of the old code.

(Another pet peeve is that fonts don’t use j3m materials so if you want to fix alpha masking or any of that you have to perform traversal surgery on the font.)

Anyway, it would be nice to have a code base that is easier to maintain because the current jme.font package is nearly impossible. I think I’m the only one that has ever managed to make a change there without breaking five other things… and I’m not patting myself on the back because I broke things plenty of times also. It’s super-fragile.

1 Like

Actually, if someone from the community would take on the job of putting together a good test suite for JME fonts as described above then I’d even be willing to devote significant time to a refactoring. Ideally, it should have a few good samples of problematic fonts, strange padding values, odd UTF ranges, etc…

If it finds existing bugs then we know it’s pretty good.

Hmm. Might be interesting…

Regarding the test suite for JME fonts, what do you have in mind?

  • fonts imported with the .fnt file format (Angel code font)?

Then for the “correct/reference” view. We would need correct way to render .fnt file.

Yeah, that’s kind of what I had in mind.

For each font being tested, we’d need the original font file and the angel code font. Render some appropriate test text with JME and create a BufferedImage in AWT to use Jav2D to render the same text as a reference. Perhaps inside a rectangle to show text bounds, multiple alignment versions, etc…

We aren’t looking for pixel-to-pixel accuracy but the text should look like and layout the same on both sides… which is why I think a side-by-side view should be ok.

1 Like

Ah okay I see. I had in mind to render the font from “fnt” file as reference. (eg. by using libgdx or some other lib as unfortunately swing does not support loading .fnt afaik).

While your idea is to use swing load the same font & size and render it with awt, which is simpler, and should be good enough for a reference.

Well okay, I can try to generate a set of fonts with Hiero, do a simple reference view and see what problems it can find. (one i already know - importer ignores padding completely)

1 Like

Could you provide an image about that “padding” thing?
I used BitmapText quite a lot and don’t know what you mean.

For the protected stuff - getters/setters would be okay.
The current state just sucks with everything private though.
For example: In order to clone one BitmapFont, I had to write a very ugly routine like this:

/**
 * Static helper class with convenience methods for BitmapFont manipulations.
 * 
 * @author Ogli
 */
public class BitmapFonts {
    
    //Java logging API
    private static final Logger logger = Logger.getLogger(BitmapFonts.class.getName());
    

    /**
     * Create a clone (there is no clone mechanism in BitmapFont itself). This 
     * is variant 1: It does not clone BitmapCharactersSet, only clones the 
     * materials ("pages"). 
     * <br>
     * NOTE: This clone is not safe when using the old 'merge' feature - if you 
     * want to use that feature, you would use the other cloning method which is 
     * {@link BitmapFonts#cloneFont2(com.jme3.font.BitmapFont)}.
     * 
     * @param font the font that you want to clone
     * @return a clone with only materials cloned
     */
    public static BitmapFont cloneFont(BitmapFont font) {

        //protect from stupid input
        if(font == null) return null; 

        //create a clone by constructing a cloned BitmapFont manually
        BitmapFont clone = new BitmapFont();
        
        //can't clone BitmapCharacterSet
        clone.setCharSet(font.getCharSet()); 
        
        //clone the material (textures and shader settings)
        Material[] pageClones = new Material[font.getPageSize()];
        for(int i = 0; i < pageClones.length; ++i) {
            pageClones[i] = font.getPage(i).clone();
        }
        clone.setPages(pageClones);

        //return the clone
        return clone;
    }

And is this cloning routine really necessary? Yes. Because: If you merge two fonts, then you can’t use them seperately anymore. And that’s just the beginning. I don’t have the time for a nitpicking argumentation/discussion about this. Sorry, the time… don’t have enough of it. Inheritence bad, composition good, yes. But sometimes just extending is sufficient.

Distancefield could be done by simply using different textures and materials - so you could (in theory) even use BitmapText and BitmapFont, change the rendering in the materials (like in the code above). But even the slightest addition or useful minimum extend of the BitmapText class is impossible at the moment…

@Ogli
In a “.fnt” file the first line contains “padding=4,4,4,4”
The current importer doesn’t read the value, thus it assumes padding is zero. For fonts with “padding=0,0,0,0” this does not cause problems.

Ah, okay.
[Bitmap Font Generator - Documentation]

So, this is a padding that is applied for every character?

Hm, didn’t need this, even for outline-fonts: Two BitmapText instances behind each other - one with outline, the other without outline - they aligned perfectly through all the xadvance and xoffset etc. that is defined per glyph and not for the whole font.

I see, that you added this in your Loader. That’s good. Still I don’t really understand what this is supposed to do - it seems to just space all glyphs a little further away from each other, which seems to be a good idea for special text effects in games. Hm… Will have to think again, but will finish current project first.

Also … an important info:

That first section, which is called “info” in AngelCode, is not really important anyways. It just tells you, how the font was generated. Though, you can get some useful information from it, the second line ("common") is much more important!

EDIT: So this “padding” may not have any important information value - it only tells you, what padding was used to generate the font? It’s not the same as a padding in CSS or HTML anyways. Which confused me a few minutes ago. :slight_smile:

When generating distance field font, (eg. by using Hiero), you setup padding otherwise the field would get cut off. So its useful there.

Here is what I know to be 100% true.

No matter what settings you use in the Angel Code software to generate the .fnt+images… the text should render properly as compared to a swing render. Most of the angle code settings are about how it’s laid out in the actual bitmap images.

You may be right about the padding issue. The issue I had with my font was the leading spacing of the first character. It’s like the loops that place the fonts ignore that leading ‘advance’.