diff --git a/client/pom.xml b/client/pom.xml index b0c3bb7..e146b39 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -164,6 +164,10 @@ org.lwjgl lwjgl-glfw + + org.lwjgl + lwjgl-nanovg + org.lwjgl lwjgl-meshoptimizer @@ -195,6 +199,11 @@ lwjgl-glfw ${lwjgl.natives} + + org.lwjgl + lwjgl-nanovg + ${lwjgl.natives} + org.lwjgl lwjgl-meshoptimizer diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java index 2535468..987113b 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java @@ -5,7 +5,7 @@ import nl.andrewl.aos2_client.Client; import nl.andrewl.aos2_client.config.ClientConfig; import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos2_client.render.chunk.ChunkRenderer; -import nl.andrewl.aos2_client.render.gui.GUIRenderer; +import nl.andrewl.aos2_client.render.gui.GuiRenderer; import nl.andrewl.aos2_client.render.gui.GUITexture; import nl.andrewl.aos2_client.render.model.Model; import nl.andrewl.aos_core.model.Team; @@ -39,7 +39,7 @@ public class GameRenderer { private final ClientConfig.DisplayConfig config; private ChunkRenderer chunkRenderer; - private GUIRenderer guiRenderer; + private GuiRenderer guiRenderer; private ModelRenderer modelRenderer; private final Camera camera; private final Client client; @@ -129,7 +129,11 @@ public class GameRenderer { this.chunkRenderer = new ChunkRenderer(); log.debug("Initialized chunk renderer."); - this.guiRenderer = new GUIRenderer(); + try { + this.guiRenderer = new GuiRenderer(); + } catch (IOException e) { + throw new RuntimeException(e); + } crosshairTexture = new GUITexture("gui/crosshair.png"); clipTexture = new GUITexture("gui/clip.png"); bulletTexture = new GUITexture("gui/bullet.png"); @@ -192,7 +196,7 @@ public class GameRenderer { } public void draw() { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw()); ClientPlayer myPlayer = client.getMyPlayer(); @@ -324,6 +328,7 @@ public class GameRenderer { -0.90f, -0.90f ); + guiRenderer.drawNvg(screenWidth, screenHeight); guiRenderer.end(); glfwSwapBuffers(windowHandle); diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUIRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java similarity index 66% rename from client/src/main/java/nl/andrewl/aos2_client/render/gui/GUIRenderer.java rename to client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java index 6dba8df..4c2cd13 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUIRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java @@ -1,19 +1,40 @@ package nl.andrewl.aos2_client.render.gui; import nl.andrewl.aos2_client.render.ShaderProgram; +import nl.andrewl.aos2_client.util.ResourceUtils; +import nl.andrewl.aos_core.FileUtils; import org.joml.Matrix4f; import org.lwjgl.BufferUtils; +import org.lwjgl.nanovg.NVGColor; +import org.lwjgl.nanovg.NVGPaint; +import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.HashMap; import java.util.Map; +import static org.lwjgl.nanovg.NanoVG.*; +import static org.lwjgl.nanovg.NanoVGGL3.*; import static org.lwjgl.opengl.GL46.*; +import static org.lwjgl.system.MemoryUtil.memFree; /** * Manages rendering of 2D GUI components like cross-hairs, inventory stuff, etc. */ -public class GUIRenderer { +public class GuiRenderer { + private final long vgId; + private final int jetbrainsMonoFont; + private final ByteBuffer jetbrainsMonoFontData; + + private static final NVGColor colorA = NVGColor.create(); + private static final NVGColor colorB = NVGColor.create(); + private static final NVGColor colorC = NVGColor.create(); + + private static final NVGPaint paintA = NVGPaint.create(); + private static final NVGPaint paintB = NVGPaint.create(); + private static final NVGPaint paintC = NVGPaint.create(); + private final int vaoId; private final int vboId; private final int vertexCount; @@ -25,7 +46,16 @@ public class GUIRenderer { private final Map textures = new HashMap<>(); - public GUIRenderer() { + public GuiRenderer() throws IOException { + vgId = nvgCreate(NVG_ANTIALIAS); + jetbrainsMonoFontData = FileUtils.readClasspathResourceAsDirectByteBuffer("text/JetBrainsMono-Regular.ttf"); + jetbrainsMonoFont = nvgCreateFontMem( + vgId, + "jetbrains-mono", + jetbrainsMonoFontData, + 0 + ); + vaoId = glGenVertexArrays(); vboId = glGenBuffers(); FloatBuffer buffer = BufferUtils.createFloatBuffer(8); @@ -87,6 +117,17 @@ public class GUIRenderer { glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount); } + public void drawNvg(float width, float height) { + nvgBeginFrame(vgId, width, height, width / height); + nvgSave(vgId); + nvgFontSize(vgId, 60f); + nvgFontFaceId(vgId, jetbrainsMonoFont); + nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + nvgText(vgId, 50, 50, "Hello world!"); + nvgRestore(vgId); + nvgEndFrame(vgId); + } + public void end() { glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); @@ -96,9 +137,13 @@ public class GUIRenderer { } public void free() { + memFree(jetbrainsMonoFontData); + nvgDelete(vgId); for (var tex : textures.values()) tex.free(); glDeleteBuffers(vboId); glDeleteVertexArrays(vaoId); shaderProgram.free(); } + + } diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiUtils.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiUtils.java new file mode 100644 index 0000000..c22b480 --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiUtils.java @@ -0,0 +1,17 @@ +package nl.andrewl.aos2_client.render.gui; + +import org.lwjgl.nanovg.NVGColor; + +public class GuiUtils { + public static NVGColor rgba(float r, float g, float b, float a, NVGColor color) { + color.r(r); + color.g(g); + color.b(b); + color.a(a); + return color; + } + + public static boolean isBlack(NVGColor color) { + return color.r() == 0 && color.g() == 0 && color.b() == 0 && color.a() == 0; + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/util/ResourceUtils.java b/client/src/main/java/nl/andrewl/aos2_client/util/ResourceUtils.java new file mode 100644 index 0000000..01b90e1 --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/util/ResourceUtils.java @@ -0,0 +1,59 @@ +package nl.andrewl.aos2_client.util; + +import org.lwjgl.BufferUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.lwjgl.BufferUtils.createByteBuffer; +import static org.lwjgl.system.MemoryUtil.memSlice; + +public class ResourceUtils { + public static ByteBuffer ioResourceToByteBuffer(String resource, int bufferSize) throws IOException { + ByteBuffer buffer; + + Path path = Paths.get(resource); + if (Files.isReadable(path)) { + try (SeekableByteChannel fc = Files.newByteChannel(path)) { + buffer = createByteBuffer((int)fc.size() + 1); + while (fc.read(buffer) != -1) { + ; + } + } + } else { + try ( + InputStream source = ResourceUtils.class.getClassLoader().getResourceAsStream(resource); + ReadableByteChannel rbc = Channels.newChannel(source) + ) { + buffer = createByteBuffer(bufferSize); + + while (true) { + int bytes = rbc.read(buffer); + if (bytes == -1) { + break; + } + if (buffer.remaining() == 0) { + buffer = resizeBuffer(buffer, buffer.capacity() * 3 / 2); // 50% + } + } + } + } + + buffer.flip(); + return memSlice(buffer); + } + + private static ByteBuffer resizeBuffer(ByteBuffer buffer, int newCapacity) { + ByteBuffer newBuffer = BufferUtils.createByteBuffer(newCapacity); + buffer.flip(); + newBuffer.put(buffer); + return newBuffer; + } +} diff --git a/client/src/main/resources/text/JetBrainsMono-Regular.ttf b/client/src/main/resources/text/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000..8da8aa4 Binary files /dev/null and b/client/src/main/resources/text/JetBrainsMono-Regular.ttf differ diff --git a/core/src/main/java/nl/andrewl/aos_core/FileUtils.java b/core/src/main/java/nl/andrewl/aos_core/FileUtils.java index 3dac464..0530b34 100644 --- a/core/src/main/java/nl/andrewl/aos_core/FileUtils.java +++ b/core/src/main/java/nl/andrewl/aos_core/FileUtils.java @@ -2,14 +2,40 @@ package nl.andrewl.aos_core; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; public final class FileUtils { private FileUtils() {} + /** + * Reads a classpath resource as a string. + * @param resource The resource to read. + * @return The string contents of the resource. + * @throws IOException If the resource can't be found or read. + */ public static String readClasspathFile(String resource) throws IOException { try (InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resource)) { if (in == null) throw new IOException("Could not load classpath resource: " + resource); return new String(in.readAllBytes()); } } + + /** + * Reads a classpath resource into a directly-allocated byte buffer that + * must be deallocated manually. + * @param resource The resource to read. + * @return The byte buffer containing the resource. + * @throws IOException If the resource can't be found or read. + */ + public static ByteBuffer readClasspathResourceAsDirectByteBuffer(String resource) throws IOException { + try (InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resource)) { + if (in == null) throw new IOException("Could not load classpath resource: " + resource); + byte[] bytes = in.readAllBytes(); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length).order(ByteOrder.nativeOrder()); + buffer.put(bytes); + buffer.flip(); + return buffer; + } + } } diff --git a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java index 393b4e3..656e4b3 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java @@ -20,10 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.Socket; +import java.net.*; import java.util.LinkedList; /** @@ -118,6 +115,8 @@ public class ClientCommunicationHandler { .withShutdownHook(() -> server.getPlayerManager().deregister(this.player)); new Thread(tcpReceiver).start(); } + } catch (SocketTimeoutException e) { + // Ignore this one, since this will happen if the client doesn't send data properly. } catch (IOException e) { log.warn("An IOException occurred while attempting to establish a connection to a client.", e); }