Browse Source

improved chat rendering

Kajetan Johannes Hammerle 2 years ago
parent
commit
142d1cdc0f

+ 129 - 0
client/Chat.cpp

@@ -0,0 +1,129 @@
+#include "client/Chat.h"
+#include "client/Controls.h"
+#include "client/FontRenderer.h"
+#include "common/NetworkPackets.h"
+#include "data/RingBuffer.h"
+#include "network/Client.h"
+#include "rendering/GL.h"
+
+static constexpr int LINES = 20;
+static RingBuffer<Chat::Message, LINES> chat;
+static bool active = false;
+static int alpha = 255;
+
+static int getAlpha() {
+    return alpha > 255 ? 255 : alpha;
+}
+
+static void enable() {
+    Window::Input::enable();
+    Window::freeCursor();
+    active = true;
+}
+
+static void disable() {
+    Window::Input::disable();
+    Window::trapCursor();
+    active = false;
+}
+
+void Chat::tick() {
+    if(active) {
+        alpha = 500;
+        if(Controls::wasReleased(Controls::enter)) {
+            OutPacket out(100);
+            out.writeU8(static_cast<uint8>(ToServerPacket::CHAT));
+            for(uint32 u : Window::Input::getUnicode()) {
+                out.writeU32(u);
+            }
+            Client::send(out, PacketSendMode::RELIABLE);
+            Window::Input::reset();
+            disable();
+        }
+        if(Controls::wasReleased(Controls::escape)) {
+            disable();
+        }
+    } else {
+        alpha = std::max(alpha - 5, 0);
+        if(Controls::isDown(Controls::chat)) {
+            enable();
+        }
+    }
+}
+
+void Chat::render() {
+    GL::disableDepthTesting();
+    GL::enableBlending();
+
+    Vector2 size = Window::getSize().toFloat();
+    IntVector2 padding(2.0f, 2.0f);
+
+    FontRenderer::bind();
+    FontRenderer::setProjection(Matrix());
+
+    Matrix v;
+    v.scale(Vector3(2.0f / size[0], -2.0f / size[1], 1.0f));
+    v.translate(Vector3(-1.0f, 1.0f, 0.0f));
+    FontRenderer::setView(v);
+
+    int scale = ((size[1] - padding[1]) / 9.0f) / (LINES + 2);
+    float y = size[1] / scale - 9.0f * (chat.getLength() + 2) - padding[1];
+
+    float max = 0.0f;
+    for(int i = 0; i < chat.getLength(); i++) {
+        max = std::max(max, FontRenderer::getWidth(chat[i], -1));
+    }
+    Matrix model;
+    model.translateTo(Vector3(1.0f, 1.0f, 0.0f)).scale(scale);
+    FontRenderer::setModel(model);
+    FontRenderer::draw(
+        StringBuffer<50>("FPS: ").append(Window::getFramesPerSecond()), 255);
+
+    model.translateTo(Vector3(padding[0] - 2.0f, y - 2.0f, 0.0f)).scale(scale);
+    FontRenderer::setModel(model);
+    if(chat.getLength() > 0) {
+        FontRenderer::draw(Vector2(max + 4.0f, 9.0f * chat.getLength() + 4.0f),
+                           Color4(0, 0, 0, getAlpha() / 2));
+    }
+
+    for(int i = 0; i < chat.getLength(); i++) {
+        model.translateTo(Vector3(padding[0], i * 9.0f + y, 0.0f)).scale(scale);
+        FontRenderer::setModel(model);
+        FontRenderer::draw(chat[i], getAlpha());
+    }
+    if(!active) {
+        return;
+    }
+    float height = (chat.getLength() + 1) * 9.0f + y;
+
+    StringBuffer<256> s;
+    Window::Input::toString(s);
+    float width = FontRenderer::getWidth(s, -1) + 8.0f;
+
+    model.translateTo(Vector3(padding[0] - 2.0f, height - 2.0f, 0.0f))
+        .scale(scale);
+    FontRenderer::setModel(model);
+    FontRenderer::draw(Vector2(width + 4.0f, 12.0f), Color4(0, 0, 0, 127));
+
+    model.translateTo(Vector3(padding[0], height, 0.0f)).scale(scale);
+    FontRenderer::setModel(model);
+    FontRenderer::draw(s, 255);
+
+    width = FontRenderer::getWidth(s, Window::Input::getCursor());
+    model.translateTo(Vector3(padding[0] + width, height + 2.0f, 0.0f))
+        .scale(scale);
+    FontRenderer::setModel(model);
+    FontRenderer::draw("_", 255);
+}
+
+bool Chat::isActive() {
+    return active;
+}
+
+void Chat::add(const Message& msg) {
+    if(chat.add(msg)) {
+        chat.remove();
+        chat.add(msg);
+    }
+    alpha = 500;
+}

+ 16 - 0
client/Chat.h

@@ -0,0 +1,16 @@
+#ifndef CHAT_H
+#define CHAT_H
+
+#include "utils/StringBuffer.h"
+
+namespace Chat {
+    typedef StringBuffer<50> Message;
+
+    void tick();
+    void render();
+
+    bool isActive();
+    void add(const Message& msg);
+}
+
+#endif

+ 178 - 0
client/FontRenderer.cpp

@@ -0,0 +1,178 @@
+#include "client/FontRenderer.h"
+#include "io/ImageReader.h"
+#include "rendering/Shader.h"
+#include "rendering/Texture.h"
+#include "rendering/VertexBuffer.h"
+#include "utils/Logger.h"
+
+static constexpr float FONT_WIDTH = 8.0f;
+static constexpr float FONT_HEIGHT = 8.0f;
+static constexpr float FONT_STEP = 1.0f / 128.0f;
+
+static Shader shader;
+static VertexBuffer buffer;
+static Texture texture;
+static Buffer data{50};
+static Color4 color;
+
+struct Symbol {
+    int offsetX = 8;
+    int width = 0;
+};
+static Array<Symbol, 256> symbols;
+
+bool FontRenderer::init() {
+    Error e = shader.compile("resources/shader/fontTest.vs",
+                             "resources/shader/fontTest.fs");
+    if(e.has()) {
+        LOG_ERROR(e.message);
+        return true;
+    }
+
+    ImageReader::Image image;
+    const char* path = "resources/font8x8.png";
+    e = ImageReader::load(image, path);
+    if(e.has()) {
+        LOG_ERROR(e.message);
+        return true;
+    }
+    if(image.channels < 1 || image.channels > 4 || image.bitdepth != 8 ||
+       image.width != 128 || image.height != 128) {
+        LOG_ERROR("font must have 1-4 channels, 8 bit per channel and a size "
+                  "of 128x128");
+        return true;
+    }
+    texture.init(Texture::Format::color8(image.channels), 0);
+    int end = image.width * image.height;
+    for(int i = 0; i < end; i++) {
+        int c = image.data[i * image.channels];
+        if(c != 255) {
+            continue;
+        }
+        int px = i % image.width;
+        int py = i / image.width;
+        int index = (py / 8) * 16 + (px / 8);
+        symbols[index].offsetX = std::min(symbols[index].offsetX, px % 8);
+        symbols[index].width = std::max(symbols[index].width, px % 8);
+    }
+    texture.setData(image.width, image.height, image.data);
+
+    for(Symbol& s : symbols) {
+        s.width -= s.offsetX - 2;
+    }
+    symbols[' '].offsetX = 0;
+    symbols[' '].width = 4;
+
+    buffer.init(VertexBuffer::Attributes().addFloat(3).addFloat(2).addColor4());
+    return false;
+}
+
+void FontRenderer::bind() {
+    shader.use();
+    texture.bindTo(0);
+}
+
+void FontRenderer::setProjection(const Matrix& m) {
+    shader.setMatrix("proj", m);
+}
+
+void FontRenderer::setView(const Matrix& m) {
+    shader.setMatrix("view", m);
+}
+
+void FontRenderer::setModel(const Matrix& m) {
+    shader.setMatrix("model", m);
+}
+
+void FontRenderer::draw(const char* text, int alpha) {
+    data.clear();
+    color = Color4(255, 255, 255, alpha);
+    int index = 0;
+    Vector3 pos;
+    int vertices = 0;
+    while(text[index] != '\0') {
+        int i = text[index];
+        if(i < 0 && text[index + 1] != '\0') {
+            index++;
+            i = ((i & 0x1F) << 6) | (text[index] & 0x3F);
+        }
+        if(i < 0 || i >= 256) {
+            continue;
+        }
+
+        Vector3 right = pos + Vector3(symbols[i].width, 0.0f, 0.0f);
+        Vector3 down = pos + Vector3(0.0f, FONT_HEIGHT, 0.0f);
+        Vector3 downRight = pos + Vector3(symbols[i].width, FONT_HEIGHT, 0.0f);
+
+        Vector2 tPos((FONT_WIDTH * (i % 16) + symbols[i].offsetX) * FONT_STEP,
+                     FONT_HEIGHT * (i / 16) * FONT_STEP);
+        Vector2 tRight = tPos + Vector2(symbols[i].width * FONT_STEP, 0.0f);
+        Vector2 tDown = tPos + Vector2(0.0f, FONT_HEIGHT * FONT_STEP);
+        Vector2 tDownRight = tPos + Vector2(symbols[i].width * FONT_STEP,
+                                            FONT_HEIGHT * FONT_STEP);
+
+        Color4 dark =
+            Color4(color[0] / 2, color[1] / 2, color[2] / 2, color[3]);
+        Vector3 shift(0.25f, 0.25f, 0.0f);
+        data.add(shift + pos).add(tPos).add(dark);
+        data.add(shift + down).add(tDown).add(dark);
+        data.add(shift + right).add(tRight).add(dark);
+        data.add(shift + down).add(tDown).add(dark);
+        data.add(shift + right).add(tRight).add(dark);
+        data.add(shift + downRight).add(tDownRight).add(dark);
+
+        data.add(pos).add(tPos).add(color);
+        data.add(down).add(tDown).add(color);
+        data.add(right).add(tRight).add(color);
+        data.add(down).add(tDown).add(color);
+        data.add(right).add(tRight).add(color);
+        data.add(downRight).add(tDownRight).add(color);
+
+        pos += Vector3(symbols[i].width, 0.0f, 0.0f);
+        vertices += 12;
+        index++;
+    }
+    buffer.setData(data, GL::DYNAMIC_DRAW);
+    buffer.draw(vertices);
+}
+
+float FontRenderer::getWidth(const char* text, int max) {
+    int index = 0;
+    float width = 0.0f;
+    while(text[index] != '\0' && (max < 0 || index < max)) {
+        int i = text[index];
+        if(i < 0 && text[index + 1] != '\0') {
+            index++;
+            i = ((i & 0x1F) << 6) | (text[index] & 0x3F);
+        }
+        if(i < 0 || i >= 256) {
+            continue;
+        }
+        width += symbols[i].width;
+        index++;
+    }
+    return width;
+}
+
+void FontRenderer::draw(const Vector2& size, Color4 c) {
+    data.clear();
+    Vector3 pos;
+    Vector3 right = pos + Vector3(size[0], 0.0f, 0.0f);
+    Vector3 down = pos + Vector3(0.0f, size[1], 0.0f);
+    Vector3 downRight = pos + Vector3(size[0], size[1], 0.0f);
+
+    Vector2 tPos;
+    Vector2 tRight(FONT_STEP, 0.0f);
+    Vector2 tDown(0.0f, FONT_STEP);
+    Vector2 tDownRight(FONT_STEP, FONT_STEP);
+
+    data.add(pos).add(tPos).add(c);
+    data.add(down).add(tDown).add(c);
+    data.add(right).add(tRight).add(c);
+    data.add(down).add(tDown).add(c);
+    data.add(right).add(tRight).add(c);
+    data.add(downRight).add(tDownRight).add(c);
+
+    buffer.setData(data, GL::DYNAMIC_DRAW);
+    buffer.draw(6);
+}

+ 18 - 0
client/FontRenderer.h

@@ -0,0 +1,18 @@
+#ifndef FONT_RENDERER_H
+#define FONT_RENDERER_H
+
+#include "math/Matrix.h"
+#include "utils/Color.h"
+
+namespace FontRenderer {
+    bool init();
+    void bind();
+    void setProjection(const Matrix& m);
+    void setView(const Matrix& m);
+    void setModel(const Matrix& m);
+    void draw(const char* text, int alpha);
+    void draw(const Vector2& size, Color4 c);
+    float getWidth(const char* text, int max);
+}
+
+#endif

+ 7 - 122
client/Main.cpp

@@ -1,36 +1,20 @@
 #include "client/Main.h"
+#include "client/Chat.h"
 #include "client/Controls.h"
+#include "client/FontRenderer.h"
 #include "client/Network.h"
 #include "client/Player.h"
 #include "client/World.h"
 #include "common/NetworkPackets.h"
-#include "data/Array.h"
-#include "data/RingBuffer.h"
-#include "math/Box.h"
 #include "math/View.h"
 #include "network/Client.h"
-#include "rendering/Shader.h"
-#include "rendering/Texture.h"
 #include "rendering/VertexBuffer.h"
 #include "rendering/Window.h"
 #include "utils/Buffer.h"
 
-static Shader fontShader;
 static VertexBuffer markVertexBuffer;
-static VertexBuffer fontBuffer;
-static Texture fontTexture;
-
 HashMap<int, OtherPlayer> players;
 
-static Array<ChatMessage, 20> chat;
-static int chatIndex = 0;
-bool renderInput = false;
-
-void addToChat(const ChatMessage& msg) {
-    chat[chatIndex] = msg;
-    chatIndex = (chatIndex + 1) % chat.getLength();
-}
-
 static bool isRunning() {
     return !Window::shouldClose();
 }
@@ -42,22 +26,10 @@ static void addTriangle(Buffer& buffer, const Vector3& a, const Vector3& b,
 }
 
 static bool init() {
-    if(World::init()) {
-        return true;
-    }
-    Error e = fontShader.compile("resources/shader/fontTest.vs",
-                                 "resources/shader/fontTest.fs");
-    if(e.has()) {
-        e.message.printLine();
-        return true;
-    }
-    e = fontTexture.load("resources/font8x8.png", 0);
-    if(e.has()) {
-        e.message.printLine();
+    if(World::init() || FontRenderer::init()) {
         return true;
     }
     markVertexBuffer.init(VertexBuffer::Attributes().addFloat(3).addFloat(3));
-    fontBuffer.init(VertexBuffer::Attributes().addFloat(3).addFloat(2));
 
     Vector3 v000(0, 0, 0);
     Vector3 v001(0, 0, 1);
@@ -88,27 +60,7 @@ static bool init() {
 }
 
 static void tick() {
-    if(renderInput) {
-        if(Controls::wasReleased(Controls::enter)) {
-            OutPacket out(100);
-            out.writeU8(static_cast<uint8>(ToServerPacket::CHAT));
-            for(uint32 u : Window::Input::getUnicode()) {
-                out.writeU32(u);
-            }
-            Client::send(out, PacketSendMode::RELIABLE);
-            Window::Input::reset();
-        }
-        if(Controls::wasReleased(Controls::escape)) {
-            Window::Input::disable();
-            Window::trapCursor();
-            renderInput = false;
-        }
-    } else {
-        if(Controls::isDown(Controls::chat)) {
-            Window::Input::enable();
-            Window::freeCursor();
-            renderInput = true;
-        }
+    if(!Chat::isActive()) {
         if(Controls::wasReleased(Controls::primaryClick)) {
             Window::trapCursor();
         }
@@ -116,14 +68,14 @@ static void tick() {
             Window::freeCursor();
         }
     }
-
+    Chat::tick();
     Player::tick();
     for(OtherPlayer& p : players.values()) {
         p.position.update();
     }
     Network::tick();
 
-    if(Controls::wasReleased(Controls::primaryClick) && !renderInput) {
+    if(Controls::wasReleased(Controls::primaryClick) && !Chat::isActive()) {
         RayTrace rt =
             World::rayTrace(Player::getPosition() + Vector3(0.0f, 0.8f, 0.0f),
                             Player::getLook(), 10.0f);
@@ -145,40 +97,6 @@ static void tick() {
     Client::send(out, PacketSendMode::RELIABLE);
 }
 
-static void renderString(const char* text) {
-    static Buffer buffer(50);
-    buffer.clear();
-    constexpr float fontSize = 8.0f;
-    constexpr float fontStep = 8.0f / 128.0f;
-    int index = 0;
-    Vector3 pos;
-    int vertices = 0;
-    while(text[index] != '\0') {
-        Vector3 right = pos + Vector3(fontSize, 0.0f, 0.0f);
-        Vector3 down = pos + Vector3(0.0f, fontSize, 0.0f);
-        Vector3 downRight = pos + Vector3(fontSize, fontSize, 0.0f);
-
-        int i = text[index];
-        if(i < 0 && text[index + 1] != '\0') {
-            index++;
-            i = ((i & 0x1F) << 6) | (text[index] & 0x3F);
-        }
-        Vector2 tPos(fontStep * (i % 16), fontStep * (i / 16));
-        Vector2 tRight = tPos + Vector2(fontStep, 0.0f);
-        Vector2 tDown = tPos + Vector2(0.0f, fontStep);
-        Vector2 tDownRight = tPos + Vector2(fontStep, fontStep);
-
-        buffer.add(pos).add(tPos).add(down).add(tDown).add(right).add(tRight);
-        buffer.add(down).add(tDown).add(right).add(tRight).add(downRight).add(
-            tDownRight);
-        pos += Vector3(fontSize, 0.0f, 0.0f);
-        vertices += 6;
-        index++;
-    }
-    fontBuffer.setData(buffer, GL::DYNAMIC_DRAW);
-    fontBuffer.draw(vertices);
-}
-
 static void render(float lag) {
     GL::clear();
     GL::enableDepthTesting();
@@ -218,40 +136,7 @@ static void render(float lag) {
     World::shader.setMatrix("model", model);
     markVertexBuffer.draw(36);
 
-    GL::disableDepthTesting();
-    GL::enableBlending();
-    fontShader.use();
-    fontShader.setMatrix("proj", Matrix());
-    Matrix v;
-    IntVector2 size = Window::getSize();
-    v.scale(Vector3(2.0f / size[0], -2.0f / size[1], 1.0f));
-    v.translate(Vector3(-1.0f, 1.0f, 0.0f));
-    fontShader.setMatrix("view", v);
-    fontTexture.bindTo(0);
-
-    for(int i = 0; i < chat.getLength(); i++) {
-        model.translateTo(Vector3(0.0f, i * 8.0f, 0.0f)).scale(3.0f);
-        fontShader.setMatrix("model", model);
-        renderString(chat[(i + chatIndex) % chat.getLength()]);
-    }
-
-    if(renderInput) {
-        model.translateTo(Vector3(0.0f, chat.getLength() * 8.0f, 0.0f))
-            .scale(3.0f);
-        fontShader.setMatrix("model", model);
-        StringBuffer<256> s;
-        Window::Input::toString(s);
-        renderString(s);
-
-        model
-            .translateTo(Vector3(Window::Input::getCursor() * 8.0f,
-                                 chat.getLength() * 8.0f + 2.0f, 0.0f))
-            .scale(3.0f);
-        fontShader.setMatrix("model", model);
-        renderString("_");
-    }
-
-    GL::disableBlending();
+    Chat::render();
 }
 
 static void startWindow() {

+ 0 - 5
client/Main.h

@@ -9,11 +9,6 @@ struct OtherPlayer {
     BufferedValue<Vector3> position;
 };
 
-typedef StringBuffer<50> ChatMessage;
-
 extern HashMap<int, OtherPlayer> players;
-extern bool renderInput;
-
-void addToChat(const ChatMessage& msg);
 
 #endif

+ 3 - 2
client/Network.cpp

@@ -1,4 +1,5 @@
 #include "client/Network.h"
+#include "client/Chat.h"
 #include "client/Main.h"
 #include "client/World.h"
 #include "common/NetworkPackets.h"
@@ -47,12 +48,12 @@ static bool onPlayerPacket(InPacket& in) {
 }
 
 static bool onChatPacket(InPacket& in) {
-    ChatMessage msg;
+    Chat::Message msg;
     uint32 u;
     while(!in.read(u)) {
         msg.appendUnicode(u);
     }
-    addToChat(msg);
+    Chat::add(msg);
     return false;
 }
 

+ 2 - 1
client/Player.cpp

@@ -1,4 +1,5 @@
 #include "client/Player.h"
+#include "client/Chat.h"
 #include "client/Controls.h"
 #include "client/Main.h"
 #include "client/World.h"
@@ -64,7 +65,7 @@ void Player::tick() {
     lengthAngle.update();
     view.updateDirections(lengthAngle, widthAngle);
 
-    if(!renderInput) {
+    if(!Chat::isActive()) {
         tickInput();
     }
 

+ 2 - 0
meson.build

@@ -15,6 +15,8 @@ src_client = [
     'client/Network.cpp',
     'client/World.cpp',
     'client/Player.cpp',
+    'client/FontRenderer.cpp',
+    'client/Chat.cpp',
 ]
 
 sources_test = ['tests/Main.cpp']

BIN
resources/font8x8.png


+ 2 - 1
resources/shader/fontTest.fs

@@ -2,10 +2,11 @@
 
 layout(binding = 0) uniform sampler2D samp;
 
+in vec4 varColor;
 in vec2 varTex;
 
 out vec4 outColor;
 
 void main(void) {
-    outColor = vec4(1.0, 0.0, 1.0, texture(samp, varTex).x);
+    outColor = vec4(varColor.xyz, varColor.w * texture(samp, varTex).x);
 }

+ 3 - 0
resources/shader/fontTest.vs

@@ -2,14 +2,17 @@
 
 layout (location = 0) in vec3 position;
 layout (location = 1) in vec2 tex;
+layout (location = 2) in vec4 color;
 
 uniform mat4 proj;
 uniform mat4 view;
 uniform mat4 model;
 
+out vec4 varColor;
 out vec2 varTex;
 
 void main(void) { 
+    varColor = color;
     varTex = tex;
     gl_Position = proj * view * model * vec4(position, 1.0);
 }