package me.hammerle.snuviengine.api; import java.nio.ByteBuffer; import me.hammerle.snuviengine.util.Rectangle; import me.hammerle.snuviengine.util.Color; import org.lwjgl.BufferUtils; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL20.*; import static org.lwjgl.opengl.GL30.*; public class FontRenderer { private final static float ERROR = 0.0f; private final static Texture[] FONT_TEXTURE = new Texture[] { new Texture("font8x8.png", true), new Texture("font16x16.png", true), new Texture("font24x24.png", true) }; public static final char COLOR_CHAR = '&'; private static final float[] COLORS = new float[128]; private static final float[] DARK_COLORS = new float[128]; private static final int FONT_SIZE = 8; private static final int LINE_STEP = 1; private static final float SHADOW_STEP = 1f; static { COLORS['0'] = Float.intBitsToFloat(Color.get(0, 0, 0)); COLORS['1'] = Float.intBitsToFloat(Color.get(0, 0, 170)); COLORS['2'] = Float.intBitsToFloat(Color.get(0, 170, 0)); COLORS['3'] = Float.intBitsToFloat(Color.get(0, 170, 170)); COLORS['4'] = Float.intBitsToFloat(Color.get(170, 0, 0)); COLORS['5'] = Float.intBitsToFloat(Color.get(170, 0, 170)); COLORS['6'] = Float.intBitsToFloat(Color.get(255, 170, 0)); COLORS['7'] = Float.intBitsToFloat(Color.get(170, 170, 170)); COLORS['8'] = Float.intBitsToFloat(Color.get(85, 85, 85)); COLORS['9'] = Float.intBitsToFloat(Color.get(85, 85, 255)); COLORS['a'] = Float.intBitsToFloat(Color.get(85, 255, 85)); COLORS['b'] = Float.intBitsToFloat(Color.get(85, 255, 255)); COLORS['c'] = Float.intBitsToFloat(Color.get(255, 85, 85)); COLORS['d'] = Float.intBitsToFloat(Color.get(255, 85, 255)); COLORS['e'] = Float.intBitsToFloat(Color.get(255, 255, 85)); COLORS['f'] = Float.intBitsToFloat(Color.get(255, 255, 255)); float factor = 0.5f; DARK_COLORS['0'] = Float.intBitsToFloat(Color.darken(Color.get(0, 0, 0), factor)); DARK_COLORS['1'] = Float.intBitsToFloat(Color.darken(Color.get(0, 0, 170), factor)); DARK_COLORS['2'] = Float.intBitsToFloat(Color.darken(Color.get(0, 170, 0), factor)); DARK_COLORS['3'] = Float.intBitsToFloat(Color.darken(Color.get(0, 170, 170), factor)); DARK_COLORS['4'] = Float.intBitsToFloat(Color.darken(Color.get(170, 0, 0), factor)); DARK_COLORS['5'] = Float.intBitsToFloat(Color.darken(Color.get(170, 0, 170), factor)); DARK_COLORS['6'] = Float.intBitsToFloat(Color.darken(Color.get(255, 170, 0), factor)); DARK_COLORS['7'] = Float.intBitsToFloat(Color.darken(Color.get(170, 170, 170), factor)); DARK_COLORS['8'] = Float.intBitsToFloat(Color.darken(Color.get(85, 85, 85), factor)); DARK_COLORS['9'] = Float.intBitsToFloat(Color.darken(Color.get(85, 85, 255), factor)); DARK_COLORS['a'] = Float.intBitsToFloat(Color.darken(Color.get(85, 255, 85), factor)); DARK_COLORS['b'] = Float.intBitsToFloat(Color.darken(Color.get(85, 255, 255), factor)); DARK_COLORS['c'] = Float.intBitsToFloat(Color.darken(Color.get(255, 85, 85), factor)); DARK_COLORS['d'] = Float.intBitsToFloat(Color.darken(Color.get(255, 85, 255), factor)); DARK_COLORS['e'] = Float.intBitsToFloat(Color.darken(Color.get(255, 255, 85), factor)); DARK_COLORS['f'] = Float.intBitsToFloat(Color.darken(Color.get(255, 255, 255), factor)); } private int vao; private int vbo; private float color; private ByteBuffer buffer = BufferUtils.createByteBuffer(OBJECT_LENGTH); private final static int MAX_LENGTH = 256; private final static int BUFFER_BYTE_LENGTH = 1024 * 1024; private final static int OBJECT_LENGTH = 120 * MAX_LENGTH * 2; private int offset = BUFFER_BYTE_LENGTH - OBJECT_LENGTH; protected FontRenderer() { Shader.addTask(() -> { vao = glGenVertexArrays(); vbo = glGenBuffers(); GLHelper.glBindVertexArray(vao); GLHelper.glBindBuffer(vbo); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); glVertexAttribPointer(0, 2, GL_FLOAT, false, 20, 0); glVertexAttribPointer(1, 2, GL_FLOAT, false, 20, 8); glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, true, 20, 16); }); } private void addRectangle(float minX, float minY, char c) { float scale = Shader.getViewScale(); minY = Math.round(minY * scale) / scale; minX = Math.round(minX * scale) / scale; float tMinX = (c & 0xF) / 16.0f; float tMinY = (c >> 4) / 16.0f; float tMaxX = tMinX + 0.0625f; float tMaxY = tMinY + 0.0625f; float maxX = minX + FONT_SIZE; float maxY = minY + FONT_SIZE; buffer.putFloat(minX); buffer.putFloat(maxY); buffer.putFloat(tMinX); buffer.putFloat(tMaxY); buffer.putFloat(color); buffer.putFloat(minX); buffer.putFloat(minY); buffer.putFloat(tMinX); buffer.putFloat(tMinY); buffer.putFloat(color); buffer.putFloat(maxX); buffer.putFloat(maxY); buffer.putFloat(tMaxX); buffer.putFloat(tMaxY); buffer.putFloat(color); buffer.putFloat(maxX); buffer.putFloat(maxY); buffer.putFloat(tMaxX); buffer.putFloat(tMaxY); buffer.putFloat(color); buffer.putFloat(minX); buffer.putFloat(minY); buffer.putFloat(tMinX); buffer.putFloat(tMinY); buffer.putFloat(color); buffer.putFloat(maxX); buffer.putFloat(minY); buffer.putFloat(tMaxX); buffer.putFloat(tMinY); buffer.putFloat(color); } public float drawString(float x, float y, boolean shadow, String s) { GLHelper.glBindBuffer(vbo); offset += OBJECT_LENGTH; if(offset + OBJECT_LENGTH >= BUFFER_BYTE_LENGTH) { offset = 0; glBufferData(GL_ARRAY_BUFFER, BUFFER_BYTE_LENGTH, GL_STREAM_DRAW); } buffer = glMapBufferRange(GL_ARRAY_BUFFER, offset, OBJECT_LENGTH, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, buffer); if(buffer == null) { return y; } if(shadow) { addString(x + SHADOW_STEP, y + SHADOW_STEP, DARK_COLORS, s); } y = addString(x, y, COLORS, s); buffer.flip(); glUnmapBuffer(GL_ARRAY_BUFFER); GLHelper.glBindVertexArray(vao); FONT_TEXTURE[Math.min(Shader.getViewScale() - 1, FONT_TEXTURE.length - 1)].bind(); glDrawArrays(GL_TRIANGLES, offset / 20, buffer.limit() / 20); buffer.limit(buffer.capacity()); return y; } public float drawString(float x, float y, String s) { return drawString(x, y, true, s); } private float addString(float x, float y, float[] colors, String s) { int l = Math.min(s.length(), MAX_LENGTH); float oldX = x; color = colors['f']; for(int pos = 0; pos < l; pos++) { char c = s.charAt(pos); if(c == COLOR_CHAR) { pos++; if(pos < l) { int index = s.charAt(pos); if(index >= 0 && index <= colors.length) { color = colors[s.charAt(pos)]; } } continue; } else if(c == '\n') { y += FONT_SIZE + LINE_STEP; x = oldX; continue; } addRectangle(x, y, c); x += FONT_SIZE; } return y + FONT_SIZE + LINE_STEP; } public Rectangle getSize(String s) { int length = 0; int counter = 1; for(int i = 0; i < s.length(); i++) { switch(s.charAt(i)) { case '\n': counter++; break; case COLOR_CHAR: i++; break; default: length++; } } return new Rectangle(FONT_SIZE * length, (FONT_SIZE + LINE_STEP) * counter); } public Rectangle getSize(int w, int h) { return new Rectangle(FONT_SIZE * w, (FONT_SIZE + LINE_STEP) * h); } public float getHeight() { return FONT_SIZE + LINE_STEP; } public float getWidth() { return FONT_SIZE; } }