6
\$\begingroup\$

I'm very sorry if this question has already been answered, but since I've been struggling with this for many days I thought I'd give it a shot here.

My goal is to somehow render some text on the screen, so I could make a score counter for my game and also maybe add some informative text. If someone could at least point me in the right direction, then I would be very grateful.

Tried to draw text using Bitmaps, but haven't had any luck doing it.

I'm using LWJGL3 and lwjgl-utils.jar for GLU.

So I got this code

package com.thecherno.flappy.font;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import java.awt.GraphicsEnvironment;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

import static org.lwjgl.glfw.GLFW.glfwGetCurrentContext;


/**
 * A TrueType font implementation originally for Slick, edited for Bobjob's Engine
 *
 * @original author James Chambers (Jimmy)
 * @original author Jeremy Adams (elias4444)
 * @original author Kevin Glass (kevglass)
 * @original author Peter Korzuszek (genail)
 *
 * @new version edited by David Aaron Muhar (bobjob)
 */
public class TrueTypeFont {
public final static int
        ALIGN_LEFT = 0,
        ALIGN_RIGHT = 1,
        ALIGN_CENTER = 2;
/** Array that holds necessary information about the font characters */
private IntObject[] charArray = new IntObject[256];

/** Map of user defined font characters (Character <-> IntObject) */
private Map customChars = new HashMap();

/** Boolean flag on whether AntiAliasing is enabled or not */
private boolean antiAlias;

/** Font's size */
private int fontSize = 0;

/** Font's height */
private int fontHeight = 0;

/** Texture used to cache the font 0-255 characters */
private int fontTextureID;

/** Default font texture width */
private int textureWidth = 512;

/** Default font texture height */
private int textureHeight = 512;

/** A reference to Java's AWT Font that we create our font texture from */
private Font font;

/** The font metrics for our Java AWT font */
private FontMetrics fontMetrics;


private int correctL = 9, correctR = 8;

private class IntObject {
    /** Character's width */
    public int width;

    /** Character's height */
    public int height;

    /** Character's stored x position */
    public int storedX;

    /** Character's stored y position */
    public int storedY;
}


public TrueTypeFont(Font font, boolean antiAlias, char[] additionalChars) {
    this.font = font;
    this.fontSize = font.getSize()+3;
    this.antiAlias = antiAlias;

    createSet( additionalChars );

    fontHeight -= 1;
    if (fontHeight <= 0) fontHeight = 1;
}

public TrueTypeFont(Font font, boolean antiAlias) {
    this( font, antiAlias, null );
}
public void setCorrection(boolean on) {
    if (on) {
        correctL = 2;
        correctR = 1;
    } else {
        correctL = 0;
        correctR = 0;
    }
}
private BufferedImage getFontImage(char ch) {
    // Create a temporary image to extract the character's size
    BufferedImage tempfontImage = new BufferedImage(1, 1,
            BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = (Graphics2D) tempfontImage.getGraphics();
    if (antiAlias == true) {
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
    }
    g.setFont(font);
    fontMetrics = g.getFontMetrics();
    int charwidth = fontMetrics.charWidth(ch)+8;

    if (charwidth <= 0) {
        charwidth = 7;
    }
    int charheight = fontMetrics.getHeight()+3;
    if (charheight <= 0) {
        charheight = fontSize;
    }

    // Create another image holding the character we are creating
    BufferedImage fontImage;
    fontImage = new BufferedImage(charwidth, charheight,
            BufferedImage.TYPE_INT_ARGB);
    Graphics2D gt = (Graphics2D) fontImage.getGraphics();
    if (antiAlias == true) {
        gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
    }
    gt.setFont(font);

    gt.setColor(Color.WHITE);
    int charx = 3;
    int chary = 1;
    gt.drawString(String.valueOf(ch), (charx), (chary)
            + fontMetrics.getAscent());

    return fontImage;

}

private void createSet( char[] customCharsArray ) {
    // If there are custom chars then I expand the font texture twice
    if  (customCharsArray != null && customCharsArray.length > 0) {
        textureWidth *= 2;
    }

    // In any case this should be done in other way. Texture with size 512x512
    // can maintain only 256 characters with resolution of 32x32. The texture
    // size should be calculated dynamicaly by looking at character sizes.

    try {

        BufferedImage imgTemp = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = (Graphics2D) imgTemp.getGraphics();

        g.setColor(new Color(0,0,0,1));
        g.fillRect(0,0,textureWidth,textureHeight);

        int rowHeight = 0;
        int positionX = 0;
        int positionY = 0;

        int customCharsLength = ( customCharsArray != null ) ? customCharsArray.length : 0;

        for (int i = 0; i < 256 + customCharsLength; i++) {

            // get 0-255 characters and then custom characters
            char ch = ( i < 256 ) ? (char) i : customCharsArray[i-256];

            BufferedImage fontImage = getFontImage(ch);

            IntObject newIntObject = new IntObject();

            newIntObject.width = fontImage.getWidth();
            newIntObject.height = fontImage.getHeight();

            if (positionX + newIntObject.width >= textureWidth) {
                positionX = 0;
                positionY += rowHeight;
                rowHeight = 0;
            }

            newIntObject.storedX = positionX;
            newIntObject.storedY = positionY;

            if (newIntObject.height > fontHeight) {
                fontHeight = newIntObject.height;
            }

            if (newIntObject.height > rowHeight) {
                rowHeight = newIntObject.height;
            }

            // Draw it here
            g.drawImage(fontImage, positionX, positionY, null);

            positionX += newIntObject.width;

            if( i < 256 ) { // standard characters
                charArray[i] = newIntObject;
            } else { // custom characters
                customChars.put( new Character( ch ), newIntObject );
            }

            fontImage = null;
        }

        fontTextureID = loadImage(imgTemp);



        //.getTexture(font.toString(), imgTemp);

    } catch (Exception e) {
        System.err.println("Failed to create font.");
        e.printStackTrace();
    }
}

private void drawQuad(float drawX, float drawY, float drawX2, float drawY2,
                      float srcX, float srcY, float srcX2, float srcY2) {
    float DrawWidth = drawX2 - drawX;
    float DrawHeight = drawY2 - drawY;
    float TextureSrcX = srcX / textureWidth;
    float TextureSrcY = srcY / textureHeight;
    float SrcWidth = srcX2 - srcX;
    float SrcHeight = srcY2 - srcY;
    float RenderWidth = (SrcWidth / textureWidth);
    float RenderHeight = (SrcHeight / textureHeight);

    GL11.glTexCoord2f(TextureSrcX, TextureSrcY);
    GL11.glVertex2f(drawX, drawY);
    GL11.glTexCoord2f(TextureSrcX, TextureSrcY + RenderHeight);
    GL11.glVertex2f(drawX, drawY + DrawHeight);
    GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY + RenderHeight);
    GL11.glVertex2f(drawX + DrawWidth, drawY + DrawHeight);
    GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY);
    GL11.glVertex2f(drawX + DrawWidth, drawY);
}

public int getWidth(String whatchars) {
    int totalwidth = 0;
    IntObject intObject = null;
    int currentChar = 0;
    for (int i = 0; i < whatchars.length(); i++) {
        currentChar = whatchars.charAt(i);
        if (currentChar < 256) {
            intObject = charArray[currentChar];
        } else {
            intObject = (IntObject)customChars.get( new Character( (char) currentChar ) );
        }

        if( intObject != null )
            totalwidth += intObject.width;
    }
    return totalwidth;
}

public int getHeight() {
    return fontHeight;
}


public int getHeight(String HeightString) {
    return fontHeight;
}

public int getLineHeight() {
    return fontHeight;
}

public void drawString(float x, float y,
                       String whatchars, float scaleX, float scaleY) {
    drawString(x,y,whatchars, 0, whatchars.length()-1, scaleX, scaleY, ALIGN_LEFT);
}
public void drawString(float x, float y,
                       String whatchars, float scaleX, float scaleY, int format) {
    drawString(x,y,whatchars, 0, whatchars.length()-1, scaleX, scaleY, format);
}


public void drawString(float x, float y,
                       String whatchars, int startIndex, int endIndex,
                       float scaleX, float scaleY,
                       int format
) {

    IntObject intObject = null;
    int charCurrent;


    int totalwidth = 0;
    int i = startIndex, d, c;
    float startY = 0;



    switch (format) {
        case ALIGN_RIGHT: {
            d = -1;
            c = correctR;

            while (i < endIndex) {
                if (whatchars.charAt(i) == '\n') startY -= fontHeight;
                i++;
            }
            break;
        }
        case ALIGN_CENTER: {
            for (int l = startIndex; l <= endIndex; l++) {
                charCurrent = whatchars.charAt(l);
                if (charCurrent == '\n') break;
                if (charCurrent < 256) {
                    intObject = charArray[charCurrent];
                } else {
                    intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
                }
                totalwidth += intObject.width-correctL;
            }
            totalwidth /= -2;
        }
        case ALIGN_LEFT:
        default: {
            d = 1;
            c = correctL;
            break;
        }

    }

    GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureID);
    GL11.glBegin(GL11.GL_QUADS);

    while (i >= startIndex && i <= endIndex) {

        charCurrent = whatchars.charAt(i);
        if (charCurrent < 256) {
            intObject = charArray[charCurrent];
        } else {
            intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
        }

        if( intObject != null ) {
            if (d < 0) totalwidth += (intObject.width-c) * d;
            if (charCurrent == '\n') {
                startY -= fontHeight * d;
                totalwidth = 0;
                if (format == ALIGN_CENTER) {
                    for (int l = i+1; l <= endIndex; l++) {
                        charCurrent = whatchars.charAt(l);
                        if (charCurrent == '\n') break;
                        if (charCurrent < 256) {
                            intObject = charArray[charCurrent];
                        } else {
                            intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
                        }
                        totalwidth += intObject.width-correctL;
                    }
                    totalwidth /= -2;
                }
                //if center get next lines total width/2;
            }
            else {
                drawQuad((totalwidth + intObject.width) * scaleX + x, startY * scaleY + y,
                        totalwidth * scaleX + x,
                        (startY + intObject.height) * scaleY + y, intObject.storedX + intObject.width,
                        intObject.storedY + intObject.height,intObject.storedX,
                        intObject.storedY);
                if (d > 0) totalwidth += (intObject.width-c) * d ;
            }
            i += d;

        }
    }
    GL11.glEnd();
}
public static int loadImage(BufferedImage bufferedImage) {
    try {
        short width       = (short)bufferedImage.getWidth();
        short height      = (short)bufferedImage.getHeight();
        //textureLoader.bpp = bufferedImage.getColorModel().hasAlpha() ? (byte)32 : (byte)24;
        int bpp = (byte)bufferedImage.getColorModel().getPixelSize();
        ByteBuffer byteBuffer;
        DataBuffer db = bufferedImage.getData().getDataBuffer();
        if (db instanceof DataBufferInt) {
            int intI[] = ((DataBufferInt)(bufferedImage.getData().getDataBuffer())).getData();
            byte newI[] = new byte[intI.length * 4];
            for (int i = 0; i < intI.length; i++) {
                byte b[] = intToByteArray(intI[i]);
                int newIndex = i*4;

                newI[newIndex]   = b[1];
                newI[newIndex+1] = b[2];
                newI[newIndex+2] = b[3];
                newI[newIndex+3] = b[0];
            }

            byteBuffer  = ByteBuffer.allocateDirect(
                    width*height*(bpp/8))
                    .order(ByteOrder.nativeOrder())
                    .put(newI);
        } else {
            byteBuffer  = ByteBuffer.allocateDirect(
                    width*height*(bpp/8))
                    .order(ByteOrder.nativeOrder())
                    .put(((DataBufferByte)(bufferedImage.getData().getDataBuffer())).getData());
        }
        byteBuffer.flip();


        int internalFormat = GL11.GL_RGBA8,
                format = GL11.GL_RGBA;
        IntBuffer   textureId =  BufferUtils.createIntBuffer(1);;
        GL11.glGenTextures(textureId);
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId.get(0));


        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);

        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);

        GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);



        GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D,
                internalFormat,
                width,
                height,
                format,
                GL11.GL_UNSIGNED_BYTE,
                byteBuffer);
        return textureId.get(0);

    } catch (Exception e) {
        e.printStackTrace();
        System.exit(-1);
    }

    return -1;
}
public static boolean isSupported(String fontname) {
    Font font[] = getFonts();
    for (int i = font.length-1; i >= 0; i--) {
        if (font[i].getName().equalsIgnoreCase(fontname))
            return true;
    }
    return false;
}
public static Font[] getFonts() {
    return GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
}
public static byte[] intToByteArray(int value) {
    return new byte[] {
            (byte)(value >>> 24),
            (byte)(value >>> 16),
            (byte)(value >>> 8),
            (byte)value};
}

public void destroy() {
    IntBuffer scratch = BufferUtils.createIntBuffer(1);
    scratch.put(0, fontTextureID);
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
    GL11.glDeleteTextures(scratch);
}
}

And my Main class looks like this

package com.thecherno.flappy;

import com.thecherno.flappy.font.BitmapFonts;
import com.thecherno.flappy.font.Ley2dFontLWJGL3;
import com.thecherno.flappy.font.TrueTypeFont;
import com.thecherno.flappy.graphics.Shader;
import com.thecherno.flappy.input.Input;
import com.thecherno.flappy.level.Level;
import com.thecherno.flappy.maths.Matrix4f;
import org.lwjgl.glfw.GLFWKeyCallback;
import org.lwjgl.glfw.GLFWvidmode;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.util.glu.GLU;

import java.awt.*;
import java.io.IOException;
import java.nio.ByteBuffer;

import static  org.lwjgl.opengl.GL11.*;
import static  org.lwjgl.opengl.GL13.*;
import static  org.lwjgl.system.MemoryUtil.*;

import static org.lwjgl.glfw.GLFW.*;

public class Main implements Runnable {

private int width = 1280;
private int height = 720;

private Thread thread;
private boolean running = false;

// Setting key callback
private GLFWKeyCallback keyCallback;

private long window; // A "pointer" to memory address of window(id)

private Level level;

public void start() {
    running = true;
    thread = new Thread(this, "Game");
    thread.start(); // This will run the run() method.
}

private void init() {

    if (glfwInit() != GL_TRUE) {
        System.err.println("Could not initialize GLFW!");
        return;
    }

    glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);
    window = glfwCreateWindow(width, height, "Flappy", NULL, NULL);
    if (window == NULL) {
        System.err.println("Could not  create GLFW window!");
        return;
    }

    glfwSetKeyCallback(window, keyCallback = new Input());

    ByteBuffer vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
    glfwSetWindowPos(window, (GLFWvidmode.width(vidmode) - width) / 2, (GLFWvidmode.height(vidmode) - height) / 2);

    glfwMakeContextCurrent(window);
    glfwShowWindow(window);
    GLContext.createFromCurrent();

    // GLInit
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_TEXTURE_2D); // Enable Texture Mapping
    glClearColor(0.5f,0.5f,0.5f,0f); // Black Background
    glDisable(GL_DITHER);
    glDepthFunc(GL_LESS); // Depth function less or equal
    glEnable(GL_NORMALIZE); // calculated normals when scaling
//        glEnable(GL_CULL_FACE); // prevent render of back surface
    glEnable(GL_ALPHA_TEST); // allows alpha channels or transperancy
    glAlphaFunc(GL_GREATER, 0.1f); // sets aplha function
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // High quality visuals
    glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); //  Really Nice Perspective Calculations
    glShadeModel(GL_SMOOTH); // Enable Smooth Shading
//        glViewport(0, 0, 800, 600);
    glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
    glLoadIdentity(); // Reset The Projection Matrix
    GLU.gluPerspective(30, width / (float) height, 1f, 300f);  //Aspect Ratio Of The Window
    glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
    glDepthMask(true);                             // Enable Depth Mask
    //

    glEnable(GL_DEPTH_TEST);
    glActiveTexture(GL_TEXTURE1);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    System.out.println("OpenGL: " + glGetString(GL_VERSION));

    Shader.loadAll();
    Shader.BG.enable();

    Matrix4f pr_matrix = Matrix4f.orthographic(-10.0f, 10.0f, -10.0f * 9.0f / 16.0f, 10.0f * 9.0f / 16.0f, -1.0f, 1.0f);
    Shader.BG.setUniformMat4f("pr_matrix", pr_matrix);
    Shader.BG.setUniform1i("tex", 1);

    Shader.BIRD.setUniformMat4f("pr_matrix", pr_matrix);
    Shader.BIRD.setUniform1i("tex", 1);

    Shader.PIPE.setUniformMat4f("pr_matrix", pr_matrix);
    Shader.PIPE.setUniform1i("tex", 1);

    Shader.IGTITLE.setUniformMat4f("pr_matrix", pr_matrix);
    Shader.IGTITLE.setUniform1i("tex", 1);

    Shader.BG.disable();

    level = new Level();
}

public void run() {
    init();

    long lastTime = System.nanoTime();
    double delta = 0.0;
    double ns = 1000000000.0 / 60.0;
    long timer = System.currentTimeMillis();
    int updates = 0;
    int frames = 0;

    while (running) {
        long now = System.nanoTime();
        delta += (now - lastTime) / ns;
        lastTime = now;
        if (delta >= 1.0) {
            update();
            updates++;
            delta--;
        }
        render();
        frames++;
        if (System.currentTimeMillis() - timer > 1000) {
            timer += 1000;
            System.out.println(updates + " ups, " + frames + " fps");
            updates = 0;
            frames = 0;
        }

        if (glfwWindowShouldClose(window) == GL_TRUE) {
            running = false;
        }

    }
    // Key callback thingy.
    keyCallback.release();
    glfwDestroyWindow(window);
    glfwTerminate();
}

private void update() {
    glfwPollEvents();
    /*if (Input.keys[GLFW_KEY_SPACE]) {
        System.out.println("FLAP!");
    }*/
    level.update();

    // Press Escape to exit the game.
    if (Input.isKeyDown(GLFW_KEY_ESCAPE)) {
        glfwDestroyWindow(window);
        glfwTerminate();
        System.exit(0);
    }

    if (level.isGameOver()) {
        level = new Level();
    }
}

private void render() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    level.render();

    glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
    glPushMatrix();                                     // Store The Projection Matrix
    glLoadIdentity();                                   // Reset The Projection Matrix
    glOrtho(0, width, 0, height, -1, 1);                          // Set Up An Ortho Screen
    glMatrixMode(GL_MODELVIEW);                         // Select The Modelview Matrix
    glPushMatrix();                                     // Store The Modelview Matrix
    glLoadIdentity();

    TrueTypeFont trueTypeFont;
    String fontName = "Agent Orange";
    if (!TrueTypeFont.isSupported(fontName)) fontName = "serif";
    Font font = new Font(fontName, Font.ITALIC | Font.BOLD, 40);
    trueTypeFont = new TrueTypeFont(font, true);
    //DRAW STRING FROM THE - CENTER
    //HALF SCALE OF 0.5f
    trueTypeFont.drawString(width / 2, trueTypeFont.getHeight() * 3,
            "at the possible sound of your name.\n" +
                    "No I wouldnt go that far.\n" +
                    "No.", 0.5f, 0.5f, TrueTypeFont.ALIGN_CENTER);

    int error = glGetError();

    if (error != GL_NO_ERROR) {
        System.out.println(error);
    }
    glfwSwapBuffers(window);
}

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

For rendering text I added this code in the Main class

    TrueTypeFont trueTypeFont;
    String fontName = "Agent Orange";
    if (!TrueTypeFont.isSupported(fontName)) fontName = "serif";
    Font font = new Font(fontName, Font.ITALIC | Font.BOLD, 40);
    trueTypeFont = new TrueTypeFont(font, true);
    //DRAW STRING FROM THE - CENTER
    //HALF SCALE OF 0.5f
    trueTypeFont.drawString(width / 2, trueTypeFont.getHeight() * 3,
            "at the possible sound of your name.\n" +
                    "No I wouldnt go that far.\n" +
                    "No.", 0.5f, 0.5f, TrueTypeFont.ALIGN_CENTER);

At the moment all I get are some white rectangles, so I assume that something is rendering. I've read that rendering white boxes is somehow a typical problem, but I can't find a solution. I'm not using any resources like .ttf or .png fonts/bitmaps for this code, should I? How?

Also, sorry for my english. I would be forever grateful if someone could help me out here.

Best wishes to all of You!

\$\endgroup\$
1
  • \$\begingroup\$ A friend sent me the post to this link. I hate seeing posts that say "do it a different way" and I know you are using Glu and trying to use TrueTypeFont, but if you aren't opposed to rolling out your own text renderer I can share some code that will do most of what you want. \$\endgroup\$ Jan 13, 2017 at 16:42

1 Answer 1

0
\$\begingroup\$

Don't know if still relevant, just wanted to update you that I had the very same issue and this fixed it:

(this is not my answer, all credits go to adventurerOK @ https://stackoverflow.com/questions/10064804/java-lwjgl-slick-unicodefont-acting-up)

It seems I have this error now, it will only draw the outlined rectangle before using the font. This seems to be a Slick error, and is a major problem so they should fix it before adding new features. The only work-around is to render fonts last on your frame.

EDIT: Problem Solved!

add this line after you render your font:

GL11.glDisable(GL11.GL_TEXTURE_2D);

The Slick people should really add that line in as it causes so many bugs!

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .