|
@@ -1,268 +1,224 @@
|
|
|
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 me.hammerle.snuviengine.util.Rectangle;
|
|
|
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
|
|
|
-{
|
|
|
- public static final char COLOR_CHAR = '&';
|
|
|
- private static final float[] COLORS = new float[128];
|
|
|
- private static final float[] DARK_COLORS = new float[128];
|
|
|
+public final class FontRenderer {
|
|
|
+ private static final char COLOR_CHAR = '#';
|
|
|
private static final int FONT_SIZE = 8;
|
|
|
private static final int LINE_STEP = 1;
|
|
|
private static final float SHADOW_STEP = 1.0f;
|
|
|
- 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;
|
|
|
-
|
|
|
- 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 final Texture[] FONT_TEXTURE = new Texture[]
|
|
|
- {
|
|
|
- new Texture("font8x8.png", true),
|
|
|
- new Texture("font16x16.png", true),
|
|
|
+ private final static int BUFFER_BYTE_LENGTH = 4 * 1024 * 1024; // 4 MiB
|
|
|
+
|
|
|
+ private final Texture[] FONT_TEXTURE = new Texture[]{
|
|
|
+ new Texture("font8x8.png", true),
|
|
|
+ new Texture("font16x16.png", true),
|
|
|
new Texture("font24x24.png", true)
|
|
|
};
|
|
|
-
|
|
|
- private final int vao;
|
|
|
- private final int vbo;
|
|
|
-
|
|
|
- private float color = 0.0f;
|
|
|
- private int scale = 1;
|
|
|
- private ByteBuffer buffer = BufferUtils.createByteBuffer(OBJECT_LENGTH);
|
|
|
- private int offset = BUFFER_BYTE_LENGTH - OBJECT_LENGTH;
|
|
|
-
|
|
|
- protected FontRenderer()
|
|
|
- {
|
|
|
- vao = glGenVertexArrays();
|
|
|
- vbo = glGenBuffers();
|
|
|
|
|
|
- GLHelper.glBindVertexArray(vao);
|
|
|
- GLHelper.glBindBuffer(vbo);
|
|
|
+ private final int vertexArray;
|
|
|
+ private final int vertexBuffer;
|
|
|
+ private int color = 0;
|
|
|
+ private int scale = 1;
|
|
|
+ private ByteBuffer buffer;
|
|
|
+ private int offset = BUFFER_BYTE_LENGTH;
|
|
|
+ private int width = 0;
|
|
|
+ private int height = 0;
|
|
|
|
|
|
+ protected FontRenderer() {
|
|
|
+ vertexArray = glGenVertexArrays();
|
|
|
+ vertexBuffer = glGenBuffers();
|
|
|
+ GLHelper.glBindVertexArray(vertexArray);
|
|
|
+ GLHelper.glBindBuffer(vertexBuffer);
|
|
|
glEnableVertexAttribArray(0);
|
|
|
- glEnableVertexAttribArray(1);
|
|
|
+ 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);
|
|
|
}
|
|
|
-
|
|
|
- protected void setScale(int scale)
|
|
|
- {
|
|
|
+
|
|
|
+ protected void setScale(int scale) {
|
|
|
this.scale = scale;
|
|
|
}
|
|
|
|
|
|
- private void addRectangle(float minX, float minY, char c)
|
|
|
- {
|
|
|
- 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)
|
|
|
- {
|
|
|
+ public float drawString(float x, float y, boolean shadow, String text) {
|
|
|
+ buffer = getNextBuffer(120 * 256 * 2);
|
|
|
+ if(buffer == null) {
|
|
|
return y;
|
|
|
}
|
|
|
-
|
|
|
- if(shadow)
|
|
|
- {
|
|
|
- addString(x + SHADOW_STEP, y + SHADOW_STEP, DARK_COLORS, s);
|
|
|
+ if(shadow) {
|
|
|
+ addStringToBuffer(x + SHADOW_STEP, y + SHADOW_STEP, true, text);
|
|
|
}
|
|
|
- y = addString(x, y, COLORS, s);
|
|
|
-
|
|
|
- buffer.flip();
|
|
|
- glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
|
-
|
|
|
- GLHelper.glBindVertexArray(vao);
|
|
|
- FONT_TEXTURE[Math.min(scale - 1, FONT_TEXTURE.length - 1)].bind();
|
|
|
-
|
|
|
- glDrawArrays(GL_TRIANGLES, offset / 20, buffer.limit() / 20);
|
|
|
-
|
|
|
- buffer.limit(buffer.capacity());
|
|
|
+ y = addStringToBuffer(x, y, false, text);
|
|
|
+ finishBuffer();
|
|
|
+ drawBuffer();
|
|
|
return y;
|
|
|
}
|
|
|
-
|
|
|
- public float drawString(float x, float y, String s)
|
|
|
- {
|
|
|
- return drawString(x, y, true, s);
|
|
|
+
|
|
|
+ public float drawString(float x, float y, String text) {
|
|
|
+ return drawString(x, y, true, text);
|
|
|
}
|
|
|
|
|
|
- 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'];
|
|
|
+ private ByteBuffer getNextBuffer(int byteLength) {
|
|
|
+ GLHelper.glBindBuffer(vertexBuffer);
|
|
|
+ offset += byteLength;
|
|
|
+ if(offset + byteLength >= BUFFER_BYTE_LENGTH) {
|
|
|
+ offset = 0;
|
|
|
+ glBufferData(GL_ARRAY_BUFFER, BUFFER_BYTE_LENGTH, GL_STREAM_DRAW);
|
|
|
+ }
|
|
|
+ return glMapBufferRange(GL_ARRAY_BUFFER, offset, byteLength, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, buffer);
|
|
|
+ }
|
|
|
|
|
|
- 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;
|
|
|
+ private float addStringToBuffer(float x, float y, boolean shadow, String text) {
|
|
|
+ float oldX = x;
|
|
|
+ setColor(0xFFFFFF, shadow);
|
|
|
+ int index = 0;
|
|
|
+ while(true) {
|
|
|
+ index += consumeColor(text, index, shadow);
|
|
|
+ if(index >= text.length()) {
|
|
|
+ break;
|
|
|
}
|
|
|
- else if(c == '\n')
|
|
|
- {
|
|
|
+ char c = text.charAt(index);
|
|
|
+ if(c == '\n') {
|
|
|
y += FONT_SIZE + LINE_STEP;
|
|
|
x = oldX;
|
|
|
- continue;
|
|
|
+ } else {
|
|
|
+ addRectangle(x, y, c);
|
|
|
+ x += FONT_SIZE;
|
|
|
}
|
|
|
-
|
|
|
- addRectangle(x, y, c);
|
|
|
- x += FONT_SIZE;
|
|
|
+ index++;
|
|
|
}
|
|
|
return y + FONT_SIZE + LINE_STEP;
|
|
|
}
|
|
|
-
|
|
|
- public Rectangle getSize(String s)
|
|
|
- {
|
|
|
- int longestLine = 0;
|
|
|
- int length = 0;
|
|
|
- int counter = 1;
|
|
|
- for(int i = 0; i < s.length(); i++)
|
|
|
- {
|
|
|
- switch(s.charAt(i))
|
|
|
- {
|
|
|
- case '\n':
|
|
|
- counter++;
|
|
|
- if(length > longestLine)
|
|
|
- {
|
|
|
- longestLine = length;
|
|
|
- }
|
|
|
- length = 0;
|
|
|
- break;
|
|
|
- case COLOR_CHAR:
|
|
|
- i++;
|
|
|
- break;
|
|
|
- default:
|
|
|
- length++;
|
|
|
- }
|
|
|
+
|
|
|
+ private void setColor(int color, boolean shadow) {
|
|
|
+ if(shadow) {
|
|
|
+ this.color = Color.darken(color, 0.5f);
|
|
|
+ } else {
|
|
|
+ this.color = color;
|
|
|
}
|
|
|
- if(length > longestLine)
|
|
|
- {
|
|
|
- longestLine = length;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int consumeColor(String text, int index, boolean shadow) {
|
|
|
+ char c = getChar(text, index);
|
|
|
+ if(c != COLOR_CHAR) {
|
|
|
+ return 0;
|
|
|
+ } else if(getChar(text, index + 1) == COLOR_CHAR) {
|
|
|
+ return 1;
|
|
|
+ } else if(text.length() < index + 7) {
|
|
|
+ return 0;
|
|
|
}
|
|
|
- return new Rectangle(FONT_SIZE * longestLine, (FONT_SIZE + LINE_STEP) * counter);
|
|
|
+ setColor(parseColor(text, index + 1), shadow);
|
|
|
+ return 7;
|
|
|
}
|
|
|
-
|
|
|
- public Rectangle getSize(int w, int h)
|
|
|
- {
|
|
|
- return new Rectangle(FONT_SIZE * w, (FONT_SIZE + LINE_STEP) * h);
|
|
|
+
|
|
|
+ private char getChar(String text, int index) {
|
|
|
+ return index < text.length() ? text.charAt(index) : '\0';
|
|
|
}
|
|
|
-
|
|
|
- public float getHeight()
|
|
|
- {
|
|
|
- return FONT_SIZE + LINE_STEP;
|
|
|
+
|
|
|
+ private int parseColor(String text, int index) {
|
|
|
+ int c = 0;
|
|
|
+ c += getCharValue(text.charAt(index)) << 4;
|
|
|
+ c += getCharValue(text.charAt(index + 1));
|
|
|
+ c += getCharValue(text.charAt(index + 2)) << 12;
|
|
|
+ c += getCharValue(text.charAt(index + 3)) << 8;
|
|
|
+ c += getCharValue(text.charAt(index + 4)) << 20;
|
|
|
+ c += getCharValue(text.charAt(index + 5)) << 16;
|
|
|
+ return c;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getCharValue(char c) {
|
|
|
+ if(c >= '0' && c <= '9') {
|
|
|
+ return c - '0';
|
|
|
+ } else if(c >= 'A' && c <= 'F') {
|
|
|
+ return c - 'A' + 10;
|
|
|
+ } else if(c >= 'a' && c <= 'f') {
|
|
|
+ return c - 'a' + 10;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void addRectangle(float minX, float minY, char c) {
|
|
|
+ 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;
|
|
|
+ addToBuffer(minX, maxY, tMinX, tMaxY);
|
|
|
+ addToBuffer(minX, minY, tMinX, tMinY);
|
|
|
+ addToBuffer(maxX, maxY, tMaxX, tMaxY);
|
|
|
+ addToBuffer(maxX, maxY, tMaxX, tMaxY);
|
|
|
+ addToBuffer(minX, minY, tMinX, tMinY);
|
|
|
+ addToBuffer(maxX, minY, tMaxX, tMinY);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void addToBuffer(float x, float y, float tx, float ty) {
|
|
|
+ buffer.putFloat(x);
|
|
|
+ buffer.putFloat(y);
|
|
|
+ buffer.putFloat(tx);
|
|
|
+ buffer.putFloat(ty);
|
|
|
+ buffer.putInt(color);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void finishBuffer() {
|
|
|
+ buffer.flip();
|
|
|
+ glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawBuffer() {
|
|
|
+ GLHelper.glBindVertexArray(vertexArray);
|
|
|
+ FONT_TEXTURE[Math.min(scale - 1, FONT_TEXTURE.length - 1)].bind();
|
|
|
+ glDrawArrays(GL_TRIANGLES, offset / 20, buffer.limit() / 20);
|
|
|
+ }
|
|
|
+
|
|
|
+ public FontRenderer getSize(String text) {
|
|
|
+ width = 0;
|
|
|
+ height = FONT_SIZE + LINE_STEP;
|
|
|
+ int currentWidth = 0;
|
|
|
+ int index = 0;
|
|
|
+ while(true) {
|
|
|
+ index += consumeColor(text, index, false);
|
|
|
+ if(index >= text.length()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ char c = text.charAt(index);
|
|
|
+ if(c == '\n') {
|
|
|
+ width = Math.max(currentWidth, width);
|
|
|
+ currentWidth = 0;
|
|
|
+ height += FONT_SIZE + LINE_STEP;
|
|
|
+ } else {
|
|
|
+ currentWidth++;
|
|
|
+ }
|
|
|
+ index++;
|
|
|
+ }
|
|
|
+ width = Math.max(currentWidth, width) * FONT_SIZE;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getStringWidth() {
|
|
|
+ return width;
|
|
|
}
|
|
|
-
|
|
|
- public float getWidth()
|
|
|
- {
|
|
|
+
|
|
|
+ public int getStringHeight() {
|
|
|
+ return height;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Rectangle getSize(int w, int h) {
|
|
|
+ return new Rectangle(FONT_SIZE * w, (FONT_SIZE + LINE_STEP) * h);
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getCharWidth() {
|
|
|
return FONT_SIZE;
|
|
|
}
|
|
|
+
|
|
|
+ public float getCharHeight() {
|
|
|
+ return FONT_SIZE + LINE_STEP;
|
|
|
+ }
|
|
|
}
|