Cloning BitmapText 3.1

So you guys want 3.1 bug reports? Here you go:
I tried to clone a BitmapText without any text set and got this:

SEVERE: Uncaught exception thrown in Thread[jME3 Main,5,main]
java.lang.RuntimeException: Error cloning object of type:class com.jme3.font.StringBlock
	at com.jme3.util.clone.Cloner.javaClone(Cloner.java:377)
	at com.jme3.util.clone.Cloner.clone(Cloner.java:260)
	at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
	at com.jme3.font.BitmapText.cloneFields(BitmapText.java:98)
	at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
	at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
	at com.jme3.scene.Spatial.clone(Spatial.java:1360)
	at com.jme3.scene.Node.clone(Node.java:682)
at com.jme3.scene.Node.clone(Node.java:62)
at com.jme3.scene.Spatial.clone(Spatial.java:1448)
at com.jme3.font.BitmapText.clone(BitmapText.java:79)
at com.grizeldi.lsfmobile.appstates.MainMenuAppState.initialize(MainMenuAppState.java:107)
at com.jme3.app.state.AppStateManager.initializePending(AppStateManager.java:251)
at com.jme3.app.state.AppStateManager.update(AppStateManager.java:281)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:236)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:193)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:232)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalAccessException: Class com.jme3.util.clone.Cloner can not access a member of class com.jme3.font.StringBlock with modifiers "public"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.jme3.util.clone.Cloner.javaClone(Cloner.java:375)
... 18 more

I checked the source and the StringBlock class has no modifiers (is package private).

I think when they say they want “Bug Reports”, I think they mean they would like to see issues/bugs opened in the Git Issue Tracker. Having “Bugs” reported in the forum is not that useful and requires them to put in significant time to find “Bugs” in the Forum and move them into the issue system. It would be better/more helpful to the core devs if you just created a proper bug report in the Git Issue Tracker.

Also, just a “Stack Trace” is not a bug report. A bug report requires the following (to be useful):

  • Description of what you were trying to do
  • Code sample (simplified/isolated as much as possible) that demonstrates the problem
  • Stack Trace of other descriptions of the error
  • Hardware/Drivers/OS in use
  • Java Version in Use
  • Any other relevant facts

Just the above is almost zero use. If you would like to help the devs by providing bug reports, it requires some real effort to create helpful/useful bug reports.

1 Like

Thanks for pointing this out.

While I don’t want to take the wind out of @gerald_edward_butler’s well thought out post, if it’s the difference between getting some report and getting no report, then posting to the forum is fine.

The maximum amount of help is obviously to do a little more legwork as suggested… but since this particular issue was new information, I’m glad to know it even without official reports or whatever. And to be honest, sometimes a heads up here can determine whether it’s even really a bug or not or a user setup issue. (This is a real bug but still.)

I assume cloning a BitmpText that has a string set works fine? If so, given the exception, that’s kind of weird. I’d expect it to fail every time.

BitmapText is kind of an odd case of barely held together code… it’s not even readable/writeable through JME serialization. So it wouldn’t surprise me if cloning is also broken… but at least that’s probably fixable.

It gets even worse with BitmapFont…
I’m currently checking if it’s also broken on jME 3.1, but…

I just found pure horror when trying to merge two fonts and use the “style” feature.
There is no way to clone font1 and font2 before merging font1 and font2.
Not even when loading via assetManager.loadFont multiple times - it’s still the same instance.
The access to the BitmapCharSet is crap - you can’t clone it manually because it’s all private.
Which spoils making use of the font that gets merged as second (just confirmed via TestBitmapText3D).
Also they must have the exact same pt size when merging - you can trick it but it renders ugly if you do.

That just killed large portions of my nice FntTextBox which was relying on the merge-feature.
So now I need to implement cloning and proper merging and file a PR and use latest version 3.1 or 3.2.
Or I simply state that the FntTextBox can’t use merged fonts because merging fonts is essentially broken.
Such a mess.
So much time that could have gone into the all total new implementation (my glyph system).
Such a crappy piece of (insert random curse word)…
Argh, that’s frustrating…

Here is what I mean (just confirmed with latest 3.1 SDK):

/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jme3test.gui;

import com.jme3.app.SimpleApplication;
import com.jme3.app.StatsAppState;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.font.Rectangle;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;

public class TestBitmapText3D extends SimpleApplication {

    private String txtB =
    "ABCDEFGHIKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-=_+[]\\;',./{}|:<>?";

    public static void main(String[] args){
        TestBitmapText3D app = new TestBitmapText3D();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        
        //if you uncomment the following line - havok is for sure
        getStateManager().detach(getStateManager().getState(StatsAppState.class));
        
        Quad q = new Quad(6, 3);
        Geometry g = new Geometry("quad", q);
        g.setLocalTranslation(0, -3, -0.0001f);
        g.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
        rootNode.attachChild(g);

        BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt");
        
        BitmapFont fnt2 = assetManager.loadFont("Interface/Fonts/Console.fnt");
        
        fnt.setStyle(0);
        fnt2.getCharSet().setRenderedSize(fnt.getCharSet().getRenderedSize());
        fnt2.setStyle(1);
        fnt.merge(fnt2);

        BitmapText txt = new BitmapText(fnt, false);
        
        //this is the problem: fnt2 can't be used anymore..
        //BitmapText txt2 = new BitmapText(fnt2, false); rootNode.attachChild(txt2);
        
        //loading it as fnt3 does not work either
        //BitmapFont fnt3 = assetManager.loadFont("Interface/Fonts/Console.fnt");
        //BitmapText txt3 = new BitmapText(fnt3, false); rootNode.attachChild(txt3);
        
        txt.setBox(new Rectangle(0, 0, 6, 3));
        txt.setQueueBucket(Bucket.Transparent);
        txt.setSize( 0.5f );
        txt.setText(txtB);
        rootNode.attachChild(txt);
        
        txt.setVerticalAlignment(BitmapFont.VAlign.Center);
        
        txt.setText("ABCDEFG 12345");
        txt.setText(txt.getText()+" "+txt.getText());
        txt.setText(txt.getText()+" "+txt.getText());
        txt.setText(txt.getText()+" "+txt.getText());
        txt.setText(txt.getText()+" "+txt.getText());
        txt.setText(txt.getText()+" "+txt.getText());
        txt.setStyle(0, 3, 1);
    }
}

We should probably just remove the merge() method in 3.2. The whole BitmapText thing is way too fragile to get fancy like that.

Yes, I think you are right. That would be the best.

In the meantime I used a dirty trick to access a private field and made a clone (see code below).
The problem:

  1. The dirty trick might not work everywhere (e.g. not on Android).

  2. That creates a huge copy (of the BitmapCharacterSet) each time you clone it.

    /*

    • This code is under “OGLI_BSD_LICENSE”, Version “2016”
    • (see http://en-us.ogli.info/OGLI_BSD_LICENSE_2016.txt)
    • License text:
    • Copyright © 2015-2016, Ogli
    • All rights reserved.
    • Redistribution and use in source and binary forms, with or without
    • modification, are permitted provided that the following conditions are
    • met:
      1. Redistributions of source code must retain the above copyright notice,
    • this list of conditions and the following disclaimer.
      1. Redistributions in binary form must reproduce the above copyright notice,
    • this list of conditions and the following disclaimer in the documentation
    • and/or other materials provided with the distribution.
      1. Neither the name of the copyright holder nor the names of its contributors
    • may be used to endorse or promote products derived from this software without
    • specific prior written permission.
    • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
    • AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    • IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    • ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    • LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    • CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    • SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    • INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    • CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    • ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    • POSSIBILITY OF SUCH DAMAGE.
      */
      package info.ogli.gui.fnt;

    import com.jme3.font.BitmapCharacter;
    import com.jme3.font.BitmapCharacterSet;
    import com.jme3.font.BitmapFont;
    import com.jme3.material.Material;
    import com.jme3.material.RenderState;
    import com.jme3.util.IntMap;
    import com.jme3.util.IntMap.Entry;
    import info.ogli.render.material.Materials;
    import java.lang.reflect.Field;

    /**

    • Static helper class with convenience methods for BitmapFont manipulations.

    • @author Ogli
      */
      public class BitmapFonts {

      //create a clone (why is there no clone mechanism in BitmapFont?)
      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()); 
       
       //use dirty trick to access private field:
       BitmapCharacterSet orig = font.getCharSet();
       BitmapCharacterSet copy = new BitmapCharacterSet();
       IntMap<IntMap<BitmapCharacter>> styleMap;
       IntMap<BitmapCharacter> chars;
       Field styleMapField;
       try { 
           styleMapField = BitmapCharacterSet.class.getDeclaredField("characters"); 
           styleMapField.setAccessible(true);
           styleMap = (IntMap<IntMap<BitmapCharacter>>)styleMapField.get(orig);
       }
       catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex){
           throw new AssertionError();
       }
       chars = styleMap.get(0);
       for(Entry<BitmapCharacter> entry : chars) {
           copy.addCharacter(entry.getKey(), entry.getValue().clone());
       }
       clone.setCharSet(copy);
       
       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;
      

      }

      //we make the font transparent (so it can be used in 3D too)
      public static void makeTransparent(BitmapFont font)
      {
      Material pageMaterial;
      for(int i = 0; i < font.getPageSize(); i++) {
      pageMaterial = font.getPage(i);
      pageMaterial.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
      pageMaterial.getAdditionalRenderState().setDepthWrite(false);
      pageMaterial.setFloat(“AlphaDiscardThreshold”,.025f);
      pageMaterial.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
      }
      }

      //prepares a BitmapFont for 3D text display
      public static void make3D(BitmapFont font) {

       //prepare the font for 3D rendering
       int ps = font.getPageSize();
       for(int i = 0; i < ps; i++)
           Materials.makeTransparent(font.getPage(i));
      

      }

      //add x-ray (see through) for the font material
      public static void makeXRay(BitmapFont font, boolean xRayOn) {

       //set the font in x-ray mode
       int ps = font.getPageSize();
       for(int i = 0; i < ps; i++)
           Materials.makeXRay(font.getPage(i), xRayOn);
      

      }
      }