From e9bb1e2b8db5ea438fb4ec4cddd28964a567fb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Fri, 9 Oct 2020 12:07:08 +0200 Subject: [PATCH] Cleaned up terminal rendering code, in particular made it more robust when scaled. --- .../li/cil/oc2/client/gui/TerminalScreen.java | 4 +- .../cil/oc2/client/gui/terminal/Terminal.java | 372 +++++++++--------- .../oc2/client/render/font/FontRenderer.java | 42 -- .../render/font/MonospaceFontRenderer.java | 102 ----- .../oc2/client/render/font/package-info.java | 7 - .../assets/oc2/textures/font/OFL.TXT | 94 +++++ .../assets/oc2/textures/font/monospace.png | Bin 6471 -> 0 bytes .../oc2/textures/gui/screen/terminal.png | Bin 3514 -> 3515 bytes .../textures/gui/screen/terminal_focused.png | Bin 6379 -> 6271 bytes 9 files changed, 290 insertions(+), 331 deletions(-) delete mode 100644 src/main/java/li/cil/oc2/client/render/font/FontRenderer.java delete mode 100644 src/main/java/li/cil/oc2/client/render/font/MonospaceFontRenderer.java delete mode 100644 src/main/java/li/cil/oc2/client/render/font/package-info.java create mode 100644 src/main/resources/assets/oc2/textures/font/OFL.TXT delete mode 100644 src/main/resources/assets/oc2/textures/font/monospace.png diff --git a/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java b/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java index 16c0db92..e85eee20 100644 --- a/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java +++ b/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java @@ -20,11 +20,11 @@ public final class TerminalScreen extends Screen { private static final ResourceLocation BACKGROUND = new ResourceLocation(API.MOD_ID, "textures/gui/screen/terminal.png"); private static final ResourceLocation BACKGROUND_TERMINAL_FOCUSED = new ResourceLocation(API.MOD_ID, "textures/gui/screen/terminal_focused.png"); private static final int TEXTURE_SIZE = 512; - private static final int SCREEN_WIDTH = 8 + 80 * 9 / 2 + 8; + private static final int SCREEN_WIDTH = 8 + 80 * 8 / 2 + 8; private static final int SCREEN_HEIGHT = 8 + 24 * 16 / 2 + 8; private static final int TERMINAL_AREA_X = 8; private static final int TERMINAL_AREA_Y = 8; - private static final int TERMINAL_AREA_WIDTH = 80 * 9 / 2; + private static final int TERMINAL_AREA_WIDTH = 80 * 8 / 2; private static final int TERMINAL_AREA_HEIGHT = 24 * 16 / 2; private final ComputerTileEntity tileEntity; diff --git a/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java b/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java index 66da495b..b3947967 100644 --- a/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java +++ b/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java @@ -6,7 +6,6 @@ import com.mojang.blaze3d.systems.RenderSystem; import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; import li.cil.ceres.api.Serialized; import li.cil.oc2.api.API; -import li.cil.oc2.client.render.font.MonospaceFontRenderer; import net.minecraft.client.Minecraft; import net.minecraft.client.audio.SimpleSound; import net.minecraft.client.renderer.BufferBuilder; @@ -29,18 +28,6 @@ import java.util.concurrent.atomic.AtomicInteger; // Implements a couple of control sequences from here: https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences @Serialized public final class Terminal { - private static final int CHAR_WIDTH = 8; - private static final int CHAR_HEIGHT = 16; - - private static final ResourceLocation LOCATION_FONT_TEXTURE = new ResourceLocation(API.MOD_ID, "textures/font/terminus.png"); - private static final int TEXTURE_RESOLUTION = 256; - private static final int TEXTURE_COLUMNS = 16; - private static final int TEXTURE_BOLD_SHIFT = TEXTURE_COLUMNS; // Bold chars are in right half of texture. - private static final float U_SIZE = CHAR_WIDTH / (float) TEXTURE_RESOLUTION; - private static final float V_SIZE = CHAR_HEIGHT / (float) TEXTURE_RESOLUTION; - private static final float U_STEP = CHAR_WIDTH / (float) TEXTURE_RESOLUTION; - private static final float V_STEP = CHAR_HEIGHT / (float) TEXTURE_RESOLUTION; - private static final int TAB_WIDTH = 4; private static final int WIDTH = 80, HEIGHT = 24; @@ -53,27 +40,8 @@ public final class Terminal { private static final int COLOR_CYAN = 6; private static final int COLOR_WHITE = 7; - private static final int[] COLORS = { - 0x010101, // Black - 0xEE3322, // Red - 0x33DD44, // Green - 0xFFCC11, // Yellow - 0x1188EE, // Blue - 0xDD33CC, // Magenta - 0x22CCDD, // Cyan - 0xEEEEEE, // White - }; - - private static final int[] DIM_COLORS = { - 0x010101, // Black - 0x772211, // Red - 0x116622, // Green - 0x886611, // Yellow - 0x115588, // Blue - 0x771177, // Magenta - 0x116677, // Cyan - 0x777777, // White - }; + private static final int CHAR_WIDTH = 8; + private static final int CHAR_HEIGHT = 16; private static final int COLOR_MASK = 0b111; private static final int COLOR_FOREGROUND_SHIFT = 3; @@ -112,29 +80,28 @@ public final class Terminal { // Style info packed into one byte for compact storage private byte style = DEFAULT_STYLE; - private final transient Object[] lines = new Object[HEIGHT]; // Cached vertex buffers for rendering, untyped for server. + // Rendering data for client private final transient AtomicInteger dirty = new AtomicInteger(-1); - private transient Object lastMatrix; // Untyped for server. + private transient Object renderer; public Terminal() { clear(); } public int getWidth() { - return WIDTH * MonospaceFontRenderer.INSTANCE.getCharWidth(); + return WIDTH * CHAR_WIDTH; } public int getHeight() { - return HEIGHT * MonospaceFontRenderer.INSTANCE.getCharHeight(); + return HEIGHT * CHAR_HEIGHT; } @OnlyIn(Dist.CLIENT) public void render(final MatrixStack stack) { - renderBuffer(stack); - - if (System.currentTimeMillis() % 1000 > 500) { - renderCursor(stack); + if (renderer == null) { + renderer = new Renderer(this); } + ((Renderer) renderer).render(dirty, stack); } public synchronized int readInput() { @@ -433,141 +400,6 @@ public final class Terminal { } } - @OnlyIn(Dist.CLIENT) - private void renderBuffer(final MatrixStack stack) { - validateLineCache(stack); - - GlStateManager.depthMask(false); - Minecraft.getInstance().getTextureManager().bindTexture(LOCATION_FONT_TEXTURE); - - final BufferBuilder buffer = Tessellator.getInstance().getBuffer(); - for (final Object line : lines) { - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR_TEX); - buffer.setVertexState((BufferBuilder.State) line); - buffer.finishDrawing(); - WorldVertexBufferUploader.draw(buffer); - } - - GlStateManager.depthMask(true); - } - - @OnlyIn(Dist.CLIENT) - private void validateLineCache(final MatrixStack stack) { - if (!Objects.equals(lastMatrix, stack.getLast().getMatrix())) { - lastMatrix = stack.getLast().getMatrix(); - dirty.set(-1); - } - - if (dirty.get() == 0) { - return; - } - - final BufferBuilder buffer = Tessellator.getInstance().getBuffer(); - - final int mask = dirty.getAndSet(0); - for (int row = 0; row < lines.length; row++) { - if ((mask & (1 << row)) == 0) { - continue; - } - - stack.push(); - stack.translate(0, row * CHAR_HEIGHT, 0); - final Matrix4f matrix = stack.getLast().getMatrix(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR_TEX); - - float tx = 0f; - for (int col = 0, index = row * WIDTH; col < WIDTH; col++, index++) { - final int character = this.buffer[index] & 0xFF; - final byte colors = this.colors[index]; - final byte style = this.styles[index]; - - if ((style & STYLE_HIDDEN_MASK) != 0) continue; - - final int x = character % TEXTURE_COLUMNS + ((style & STYLE_BOLD_MASK) != 0 ? TEXTURE_BOLD_SHIFT : 0); - final int y = character / TEXTURE_COLUMNS; - final float u = x * U_STEP; - final float v = y * V_STEP; - - final int[] palette = (style & STYLE_DIM_MASK) != 0 ? DIM_COLORS : COLORS; - - final int foreground = palette[(colors >> COLOR_FOREGROUND_SHIFT) & COLOR_MASK]; - final int background = palette[colors & COLOR_MASK]; - - final int color = (style & STYLE_INVERT_MASK) != 0 ? background : foreground; - final float r = ((color >> 16) & 0xFF) / 255f; - final float g = ((color >> 8) & 0xFF) / 255f; - final float b = ((color) & 0xFF) / 255f; - - if (isPrintableCharacter((char) character)) { - buffer.pos(matrix, tx, CHAR_HEIGHT, 0).color(r, g, b, 1).tex(u, v + V_SIZE).endVertex(); - buffer.pos(matrix, tx + CHAR_WIDTH, CHAR_HEIGHT, 0).color(r, g, b, 1).tex(u + U_SIZE, v + V_SIZE).endVertex(); - buffer.pos(matrix, tx + CHAR_WIDTH, 0, 0).color(r, g, b, 1).tex(u + U_SIZE, v).endVertex(); - buffer.pos(matrix, tx, 0, 0).color(r, g, b, 1).tex(u, v).endVertex(); - } - - if ((style & STYLE_UNDERLINE_MASK) != 0) { - final float ulu = (TEXTURE_RESOLUTION - 1) / (float) TEXTURE_RESOLUTION; - final float ulv = 1 / (float) TEXTURE_RESOLUTION; - - buffer.pos(matrix, tx, CHAR_HEIGHT - 3, 0).color(r, g, b, 1).tex(ulu, ulv).endVertex(); - buffer.pos(matrix, tx + CHAR_WIDTH, CHAR_HEIGHT - 3, 0).color(r, g, b, 1).tex(ulu, ulv).endVertex(); - buffer.pos(matrix, tx + CHAR_WIDTH, CHAR_HEIGHT - 2, 0).color(r, g, b, 1).tex(ulu, ulv).endVertex(); - buffer.pos(matrix, tx, CHAR_HEIGHT - 2, 0).color(r, g, b, 1).tex(ulu, ulv).endVertex(); - } - - tx += CHAR_WIDTH; - } - - lines[row] = buffer.getVertexState(); - buffer.finishDrawing(); - buffer.discard(); - - stack.pop(); - } - } - - private static boolean isPrintableCharacter(final char ch) { - return ch == 0 || - (ch > ' ' && ch <= '~') || - ch >= 177; - } - - @OnlyIn(Dist.CLIENT) - private void renderCursor(final MatrixStack stack) { - if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { - return; - } - - GlStateManager.depthMask(false); - RenderSystem.disableTexture(); - - stack.push(); - stack.translate(x * CHAR_WIDTH, y * CHAR_HEIGHT, 0); - - final Matrix4f matrix = stack.getLast().getMatrix(); - final BufferBuilder buffer = Tessellator.getInstance().getBuffer(); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); - - final int foreground = COLORS[COLOR_WHITE]; - final float r = ((foreground >> 16) & 0xFF) / 255f; - final float g = ((foreground >> 8) & 0xFF) / 255f; - final float b = ((foreground) & 0xFF) / 255f; - - buffer.pos(matrix, 0, CHAR_HEIGHT, 0).color(r, g, b, 1).endVertex(); - buffer.pos(matrix, CHAR_WIDTH, CHAR_HEIGHT, 0).color(r, g, b, 1).endVertex(); - buffer.pos(matrix, CHAR_WIDTH, 0, 0).color(r, g, b, 1).endVertex(); - buffer.pos(matrix, 0, 0, 0).color(r, g, b, 1).endVertex(); - - buffer.finishDrawing(); - WorldVertexBufferUploader.draw(buffer); - - stack.pop(); - - RenderSystem.enableTexture(); - GlStateManager.depthMask(true); - } - private void setCursorPos(final int x, final int y) { this.x = Math.max(0, Math.min(WIDTH - 1, x)); this.y = Math.max(0, Math.min(HEIGHT - 1, y)); @@ -627,7 +459,6 @@ public final class Terminal { System.arraycopy(buffer, WIDTH, buffer, 0, buffer.length - WIDTH); System.arraycopy(colors, WIDTH, colors, 0, colors.length - WIDTH); System.arraycopy(styles, WIDTH, styles, 0, styles.length - WIDTH); - System.arraycopy(lines, 1, lines, 0, lines.length - 1); Arrays.fill(buffer, WIDTH * HEIGHT - WIDTH, WIDTH * HEIGHT, (byte) ' '); Arrays.fill(colors, WIDTH * HEIGHT - WIDTH, WIDTH * HEIGHT, DEFAULT_COLORS); Arrays.fill(styles, WIDTH * HEIGHT - WIDTH, WIDTH * HEIGHT, DEFAULT_STYLE); @@ -635,4 +466,189 @@ public final class Terminal { // Offset is baked into buffers so we must rebuild them all. dirty.set(-1); } + + @OnlyIn(Dist.CLIENT) + private static final class Renderer { + private static final ResourceLocation LOCATION_FONT_TEXTURE = new ResourceLocation(API.MOD_ID, "textures/font/terminus.png"); + private static final int TEXTURE_RESOLUTION = 256; + private static final float ONE_OVER_TEXTURE_RESOLUTION = 1.0f / (float) TEXTURE_RESOLUTION; + private static final int TEXTURE_COLUMNS = 16; + private static final int TEXTURE_BOLD_SHIFT = TEXTURE_COLUMNS; // Bold chars are in right half of texture. + + private static final int[] COLORS = { + 0x010101, // Black + 0xEE3322, // Red + 0x33DD44, // Green + 0xFFCC11, // Yellow + 0x1188EE, // Blue + 0xDD33CC, // Magenta + 0x22CCDD, // Cyan + 0xEEEEEE, // White + }; + + private static final int[] DIM_COLORS = { + 0x010101, // Black + 0x772211, // Red + 0x116622, // Green + 0x886611, // Yellow + 0x115588, // Blue + 0x771177, // Magenta + 0x116677, // Cyan + 0x777777, // White + }; + + private final Terminal terminal; + + private final Object[] lines = new Object[HEIGHT]; // Cached vertex buffers for rendering, untyped for server. + private Object lastMatrix; // Untyped for server. + + public Renderer(final Terminal terminal) { + this.terminal = terminal; + } + + public void render(final AtomicInteger dirty, final MatrixStack stack) { + validateLineCache(dirty, stack); + renderBuffer(); + + if (System.currentTimeMillis() % 1000 > 500) { + renderCursor(stack); + } + } + + private void renderBuffer() { + GlStateManager.depthMask(false); + Minecraft.getInstance().getTextureManager().bindTexture(LOCATION_FONT_TEXTURE); + + final BufferBuilder buffer = Tessellator.getInstance().getBuffer(); + for (final Object line : lines) { + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR_TEX); + buffer.setVertexState((BufferBuilder.State) line); + buffer.finishDrawing(); + WorldVertexBufferUploader.draw(buffer); + } + + GlStateManager.depthMask(true); + } + + private void validateLineCache(final AtomicInteger dirty, final MatrixStack stack) { + if (!Objects.equals(lastMatrix, stack.getLast().getMatrix())) { + lastMatrix = stack.getLast().getMatrix(); + dirty.set(-1); + } + + if (dirty.get() == 0) { + return; + } + + final BufferBuilder buffer = Tessellator.getInstance().getBuffer(); + + final int mask = dirty.getAndSet(0); + for (int row = 0; row < lines.length; row++) { + if ((mask & (1 << row)) == 0) { + continue; + } + + stack.push(); + stack.translate(0, row * CHAR_HEIGHT, 0); + final Matrix4f matrix = stack.getLast().getMatrix(); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR_TEX); + + float tx = 0f; + for (int col = 0, index = row * WIDTH; col < WIDTH; col++, index++) { + final int character = terminal.buffer[index] & 0xFF; + final byte colors = terminal.colors[index]; + final byte style = terminal.styles[index]; + + if ((style & STYLE_HIDDEN_MASK) != 0) continue; + + final int x = character % TEXTURE_COLUMNS + ((style & STYLE_BOLD_MASK) != 0 ? TEXTURE_BOLD_SHIFT : 0); + final int y = character / TEXTURE_COLUMNS; + // The +0.5f is to sample the center of our texture pixels. This counteracts small + // floating point inaccuracies in our texture coordinates introduced by varying + // render resolution. And since we use point sampling it doesn't matter where + // exactly inside a pixel we sample. + final float u0 = (x * CHAR_WIDTH + 0.5f) * ONE_OVER_TEXTURE_RESOLUTION; + final float u1 = ((x + 1) * CHAR_WIDTH + 0.5f) * ONE_OVER_TEXTURE_RESOLUTION; + final float v0 = (y * CHAR_HEIGHT + 0.5f) * ONE_OVER_TEXTURE_RESOLUTION; + final float v1 = ((y + 1) * CHAR_HEIGHT + 0.5f) * ONE_OVER_TEXTURE_RESOLUTION; + + final int[] palette = (style & STYLE_DIM_MASK) != 0 ? DIM_COLORS : COLORS; + + final int foreground = palette[(colors >> COLOR_FOREGROUND_SHIFT) & COLOR_MASK]; + final int background = palette[colors & COLOR_MASK]; + + final int color = (style & STYLE_INVERT_MASK) != 0 ? background : foreground; + final float r = ((color >> 16) & 0xFF) / 255f; + final float g = ((color >> 8) & 0xFF) / 255f; + final float b = ((color) & 0xFF) / 255f; + + if (isPrintableCharacter((char) character)) { + buffer.pos(matrix, tx, CHAR_HEIGHT, 0).color(r, g, b, 1).tex(u0, v1).endVertex(); + buffer.pos(matrix, tx + CHAR_WIDTH, CHAR_HEIGHT, 0).color(r, g, b, 1).tex(u1, v1).endVertex(); + buffer.pos(matrix, tx + CHAR_WIDTH, 0, 0).color(r, g, b, 1).tex(u1, v0).endVertex(); + buffer.pos(matrix, tx, 0, 0).color(r, g, b, 1).tex(u0, v0).endVertex(); + } + + if ((style & STYLE_UNDERLINE_MASK) != 0) { + final float ulu = (TEXTURE_RESOLUTION - 1) / (float) TEXTURE_RESOLUTION; + final float ulv = 1 / (float) TEXTURE_RESOLUTION; + + buffer.pos(matrix, tx, CHAR_HEIGHT - 3, 0).color(r, g, b, 1).tex(ulu, ulv).endVertex(); + buffer.pos(matrix, tx + CHAR_WIDTH, CHAR_HEIGHT - 3, 0).color(r, g, b, 1).tex(ulu, ulv).endVertex(); + buffer.pos(matrix, tx + CHAR_WIDTH, CHAR_HEIGHT - 2, 0).color(r, g, b, 1).tex(ulu, ulv).endVertex(); + buffer.pos(matrix, tx, CHAR_HEIGHT - 2, 0).color(r, g, b, 1).tex(ulu, ulv).endVertex(); + } + + tx += CHAR_WIDTH; + } + + lines[row] = buffer.getVertexState(); + buffer.finishDrawing(); + buffer.discard(); + + stack.pop(); + } + } + + private void renderCursor(final MatrixStack stack) { + if (terminal.x < 0 || terminal.x >= WIDTH || terminal.y < 0 || terminal.y >= HEIGHT) { + return; + } + + GlStateManager.depthMask(false); + RenderSystem.disableTexture(); + + stack.push(); + stack.translate(terminal.x * CHAR_WIDTH, terminal.y * CHAR_HEIGHT, 0); + + final Matrix4f matrix = stack.getLast().getMatrix(); + final BufferBuilder buffer = Tessellator.getInstance().getBuffer(); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); + + final int foreground = COLORS[COLOR_WHITE]; + final float r = ((foreground >> 16) & 0xFF) / 255f; + final float g = ((foreground >> 8) & 0xFF) / 255f; + final float b = ((foreground) & 0xFF) / 255f; + + buffer.pos(matrix, 0, CHAR_HEIGHT, 0).color(r, g, b, 1).endVertex(); + buffer.pos(matrix, CHAR_WIDTH, CHAR_HEIGHT, 0).color(r, g, b, 1).endVertex(); + buffer.pos(matrix, CHAR_WIDTH, 0, 0).color(r, g, b, 1).endVertex(); + buffer.pos(matrix, 0, 0, 0).color(r, g, b, 1).endVertex(); + + buffer.finishDrawing(); + WorldVertexBufferUploader.draw(buffer); + + stack.pop(); + + RenderSystem.enableTexture(); + GlStateManager.depthMask(true); + } + + private static boolean isPrintableCharacter(final char ch) { + return ch == 0 || + (ch > ' ' && ch <= '~') || + ch >= 177; + } + } } diff --git a/src/main/java/li/cil/oc2/client/render/font/FontRenderer.java b/src/main/java/li/cil/oc2/client/render/font/FontRenderer.java deleted file mode 100644 index 1bbdc5c6..00000000 --- a/src/main/java/li/cil/oc2/client/render/font/FontRenderer.java +++ /dev/null @@ -1,42 +0,0 @@ -package li.cil.oc2.client.render.font; - -import net.minecraft.client.renderer.Matrix4f; - -/** - * Base interface for font renderers. - */ -public interface FontRenderer { - /** - * Render the specified string. - * - * @param matrix transformation of the text to draw. - * @param value the string to render. - */ - void drawString(final Matrix4f matrix, final CharSequence value); - - /** - * Render up to the specified amount of characters of the specified string. - *

- * This is intended as a convenience method for clamped-width rendering, - * avoiding additional string operations such as substring. - * - * @param matrix transformation of the text to draw. - * @param value the string to render. - * @param maxChars the maximum number of characters to render. - */ - void drawString(final Matrix4f matrix, final CharSequence value, final int maxChars); - - /** - * Get the width of the characters drawn with the font renderer, in pixels. - * - * @return the width of the drawn characters. - */ - int getCharWidth(); - - /** - * Get the height of the characters drawn with the font renderer, in pixels. - * - * @return the height of the drawn characters. - */ - int getCharHeight(); -} diff --git a/src/main/java/li/cil/oc2/client/render/font/MonospaceFontRenderer.java b/src/main/java/li/cil/oc2/client/render/font/MonospaceFontRenderer.java deleted file mode 100644 index 86a80534..00000000 --- a/src/main/java/li/cil/oc2/client/render/font/MonospaceFontRenderer.java +++ /dev/null @@ -1,102 +0,0 @@ -package li.cil.oc2.client.render.font; - -import com.mojang.blaze3d.platform.GlStateManager; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import li.cil.oc2.api.API; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.Matrix4f; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.util.ResourceLocation; -import org.lwjgl.opengl.GL11; - -public final class MonospaceFontRenderer implements FontRenderer { - public static final FontRenderer INSTANCE = new MonospaceFontRenderer(); - - private static final ResourceLocation LOCATION_FONT_TEXTURE = new ResourceLocation(API.MOD_ID, "textures/font/monospace.png"); - private static final String CHARS = "\0\u263a\u263b\u2665\u2666\u2663\u2660\u2022\u25d8\u25cb\u25d9\u2642\u2640\u266a\u266b\u263c\u25ba\u25c4\u2195\u203c\u00b6\u00a7\u25ac\u21a8\u2191\u2193\u2192\u2190\u221f\u2194\u25b2\u25bc !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00a2\u00a3\u00a5\u20a7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u2310\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u00df\u0393\u03c0\u03a3\u03c3\u00b5\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u03c6\u03b5\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0"; - private static final int TEXTURE_RESOLUTION = 256; - - private final Int2IntMap CHAR_MAP; - - private final int COLUMNS = TEXTURE_RESOLUTION / getCharWidth(); - private final float U_SIZE = getCharWidth() / (float) TEXTURE_RESOLUTION; - private final float V_SIZE = getCharHeight() / (float) TEXTURE_RESOLUTION; - private final float U_STEP = getCharWidth() / (float) TEXTURE_RESOLUTION; - private final float V_STEP = getCharHeight() / (float) TEXTURE_RESOLUTION; - - private MonospaceFontRenderer() { - CHAR_MAP = new Int2IntOpenHashMap(); - final CharSequence chars = CHARS; - for (int index = 0; index < chars.length(); index++) { - CHAR_MAP.put(chars.charAt(index), index); - } - } - - public void drawString(final Matrix4f matrix, final CharSequence value) { - drawString(matrix, value, value.length()); - } - - public void drawString(final Matrix4f matrix, final CharSequence value, final int maxChars) { - GlStateManager.depthMask(false); - - Minecraft.getInstance().getTextureManager().bindTexture(LOCATION_FONT_TEXTURE); - - final Tessellator tessellator = Tessellator.getInstance(); - final BufferBuilder buffer = tessellator.getBuffer(); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - float tx = 0f; - final int end = Math.min(maxChars, value.length()); - for (int i = 0; i < end; i++) { - final char ch = value.charAt(i); - drawChar(matrix, tx, ch, buffer); - tx += getCharWidth(); - } - - tessellator.draw(); - - GlStateManager.depthMask(true); - } - - // --------------------------------------------------------------------- // - // FontRenderer - - @Override - public int getCharWidth() { - return 9; - } - - @Override - public int getCharHeight() { - return 16; - } - - // --------------------------------------------------------------------- // - - private void drawChar(final Matrix4f matrix, final float x, final char ch, final BufferBuilder buffer) { - if (Character.isWhitespace(ch) || Character.isISOControl(ch)) { - return; - } - final int index = getCharIndex(ch); - - final int column = index % COLUMNS; - final int row = index / COLUMNS; - final float u = column * U_STEP; - final float v = row * V_STEP; - - buffer.pos(matrix, x, getCharHeight(), 0).tex(u, v + V_SIZE).endVertex(); - buffer.pos(matrix, x + getCharWidth(), getCharHeight(), 0).tex(u + U_SIZE, v + V_SIZE).endVertex(); - buffer.pos(matrix, x + getCharWidth(), 0, 0).tex(u + U_SIZE, v).endVertex(); - buffer.pos(matrix, x, 0, 0).tex(u, v).endVertex(); - } - - private int getCharIndex(final char ch) { - if (!CHAR_MAP.containsKey(ch)) { - return CHAR_MAP.get('?'); - } - return CHAR_MAP.get(ch); - } -} diff --git a/src/main/java/li/cil/oc2/client/render/font/package-info.java b/src/main/java/li/cil/oc2/client/render/font/package-info.java deleted file mode 100644 index 215dd2d2..00000000 --- a/src/main/java/li/cil/oc2/client/render/font/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -@ParametersAreNonnullByDefault -@MethodsReturnNonnullByDefault -package li.cil.oc2.client.render.font; - -import mcp.MethodsReturnNonnullByDefault; - -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/resources/assets/oc2/textures/font/OFL.TXT b/src/main/resources/assets/oc2/textures/font/OFL.TXT new file mode 100644 index 00000000..38cbd33e --- /dev/null +++ b/src/main/resources/assets/oc2/textures/font/OFL.TXT @@ -0,0 +1,94 @@ +Copyright (c) 2019 Dimitar Toshkov Zhekov, +with Reserved Font Name "Terminus Font". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/resources/assets/oc2/textures/font/monospace.png b/src/main/resources/assets/oc2/textures/font/monospace.png deleted file mode 100644 index 31ef2e5042d6b8d6c46bff474e4b17e827a4986d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6471 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 U6sxlPZx&!`XbCIPzjgn=0HzFjv;Y7A diff --git a/src/main/resources/assets/oc2/textures/gui/screen/terminal.png b/src/main/resources/assets/oc2/textures/gui/screen/terminal.png index d08c6791e5f825b0b31d72de2b232592663d641f..eeb4f2cddd9d968e4d5afdb172d5d82a5ac85cbb 100644 GIT binary patch delta 787 zcmV+u1MK{|8@n5@wF3?@FfA}SR539+G&MRjGLyptA`USyEigG$F)=zcH99mhv-1NB z0S+-REigG$F)=zcH99mhlMDrJ1h&?Fk&}J}AAip)L{SvSKQjnpM6s~2cxfm?e$>Rq zV#H8b%#8AD^X8c!YTkIgHtceYJ&hykr=HBi-pZmS% zoX$NLys?a?Tcur~XLDvG98#vHXO!|sd{oM8qgB-`V>~(>qv&|M!QNRcK-yQq1|o)DW9J%1rH!?M%=zkRimy*+Mre+bGRZ2P4d$~WM1 z-L~KNZTs^8O77t-TZmuWgT7bM*9%%;7iw1F9w4o{yk< z8tN{=H|zH7=bpn`h^@$1UBLDZw1|Jr+!uqQXY;WyWoQ5Z010qNS#tmY3ljhU3x5*< z0GgZ_000JqNklQ{wtehe!C6;_bh7Zr7dx|HaJ!n3;KddwawVfN$q;IQVZAPs=B>!3Zbrc0DvF?0Pso2<8kc?@MT_RW?7cydOn{$+yD^q z{sv%qeSKYZ2*6KqI-O=_mbG`lzxen7UsqWH@UuAl0kcsHQUa4;3ketg;ui=vGdhZn R{6+u(002ovPDHLkV1hG^V&VV* delta 791 zcmV+y1L*v_8@d~?wF3??IW03VR53F;Ff%$eHa$}57e0P&SloBe(vAV z9Dj-G3e+?Rk148_6`m5FSZTJzV5BljBAHF04z;k$iP;R%I^B>^ZEz2?^0{DcNarguL80YhO#v5HeVVMsB zaJgL8TLZv9IUbKQGjlqfX1odDjy;#(e>nUBpyB1uw*UY?jQ{|Epx1jB_xt^$b^!22 zyWMWK+wIyjcv5!*U}oley*^?G0AF-C9Q=2SzvdIO!3ihqHL6L=!8GqWtqayy^T z9v%RQcz*-1Y&M%!hXC+BPN&n%%(C_l{%;>2klQLN0QgQ0f3r~wQv#D<3kVlr6>s~p VGdivozI6Zq002ovPDHLkV1nBTTXz5e diff --git a/src/main/resources/assets/oc2/textures/gui/screen/terminal_focused.png b/src/main/resources/assets/oc2/textures/gui/screen/terminal_focused.png index d590f576b078255b6c5adc6658c4e9b887b3c99e..cffc6334761091df85a896cad257f8bf11bc9d26 100644 GIT binary patch delta 4335 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 delta 4441 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