Added nameplate rendering.

This commit is contained in:
Andrew Lalis 2022-07-27 14:29:57 +02:00
parent 3a70a4566b
commit ec9153053c
6 changed files with 148 additions and 19 deletions

View File

@ -213,6 +213,7 @@ public class Client implements Runnable {
player.updateModelTransform(); player.updateModelTransform();
soundManager.playWalkingSounds(player, now); soundManager.playWalkingSounds(player, now);
} }
gameRenderer.getGuiRenderer().updateNamePlates(players.values());
} }
public void interpolateProjectiles(float dt) { public void interpolateProjectiles(float dt) {

View File

@ -171,6 +171,10 @@ public class GameRenderer {
return camera; return camera;
} }
public GuiRenderer getGuiRenderer() {
return guiRenderer;
}
public void draw() { public void draw() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw()); chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw());
@ -266,6 +270,7 @@ public class GameRenderer {
// GUI rendering // GUI rendering
guiRenderer.start(); guiRenderer.start();
guiRenderer.drawNameplates(myPlayer, camera.getViewTransformData(), perspectiveTransform.get(new float[16]));
guiRenderer.drawNvg(screenWidth, screenHeight, myPlayer); guiRenderer.drawNvg(screenWidth, screenHeight, myPlayer);
guiRenderer.end(); guiRenderer.end();

View File

@ -1,8 +1,11 @@
package nl.andrewl.aos2_client.render.gui; package nl.andrewl.aos2_client.render.gui;
import nl.andrewl.aos2_client.Camera;
import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos2_client.model.ClientPlayer;
import nl.andrewl.aos2_client.model.OtherPlayer;
import nl.andrewl.aos2_client.render.ShaderProgram; import nl.andrewl.aos2_client.render.ShaderProgram;
import nl.andrewl.aos_core.FileUtils; import nl.andrewl.aos_core.FileUtils;
import nl.andrewl.aos_core.model.Player;
import nl.andrewl.aos_core.model.item.BlockItem; import nl.andrewl.aos_core.model.item.BlockItem;
import nl.andrewl.aos_core.model.item.BlockItemStack; import nl.andrewl.aos_core.model.item.BlockItemStack;
import nl.andrewl.aos_core.model.item.Gun; import nl.andrewl.aos_core.model.item.Gun;
@ -12,11 +15,12 @@ import org.lwjgl.BufferUtils;
import org.lwjgl.nanovg.NVGColor; import org.lwjgl.nanovg.NVGColor;
import org.lwjgl.nanovg.NVGPaint; import org.lwjgl.nanovg.NVGPaint;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import static org.lwjgl.nanovg.NanoVG.*; import static org.lwjgl.nanovg.NanoVG.*;
import static org.lwjgl.nanovg.NanoVGGL3.*; import static org.lwjgl.nanovg.NanoVGGL3.*;
@ -49,7 +53,13 @@ public class GuiRenderer {
private final Matrix4f transformMatrix; private final Matrix4f transformMatrix;
private final float[] transformMatrixData; private final float[] transformMatrixData;
private final Map<String, GUITexture> textures = new HashMap<>(); private final ShaderProgram namePlateShaderProgram;
private final int namePlateTransformUniform;
private final int namePlateViewTransformUniform;
private final int namePlatePerspectiveTransformUniform;
private final int namePlateTextureSamplerUniform;
private final Font namePlateFont;
private final Map<OtherPlayer, GUITexture> playerNamePlates = new HashMap<>();
public GuiRenderer() throws IOException { public GuiRenderer() throws IOException {
vgId = nvgCreate(NVG_ANTIALIAS); vgId = nvgCreate(NVG_ANTIALIAS);
@ -85,20 +95,26 @@ public class GuiRenderer {
transformUniformLocation = shaderProgram.getUniform("transform"); transformUniformLocation = shaderProgram.getUniform("transform");
textureSamplerUniform = shaderProgram.getUniform("guiTexture"); textureSamplerUniform = shaderProgram.getUniform("guiTexture");
shaderProgram.bindAttribute(0, "position"); shaderProgram.bindAttribute(0, "position");
this.transformMatrix = new Matrix4f();
this.transformMatrixData = new float[16];
}
public void loadTexture(String name, String resource) { // Shader program for rendering name plates.
try { namePlateShaderProgram = new ShaderProgram.Builder()
textures.put(name, new GUITexture(resource)); .withShader("shader/gui/nameplate_vertex.glsl", GL_VERTEX_SHADER)
} catch (IOException e) { .withShader("shader/gui/nameplate_fragement.glsl", GL_FRAGMENT_SHADER)
.build();
namePlateTransformUniform = namePlateShaderProgram.getUniform("transform");
namePlateViewTransformUniform = namePlateShaderProgram.getUniform("viewTransform");
namePlatePerspectiveTransformUniform = namePlateShaderProgram.getUniform("perspectiveTransform");
namePlateTextureSamplerUniform = namePlateShaderProgram.getUniform("guiTexture");
namePlateShaderProgram.bindAttribute(0, "vertexPosition");
try (var in = FileUtils.getClasspathResource("text/JetBrainsMono-Regular.ttf")) {
namePlateFont = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(84f);
} catch (FontFormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}
public void addTexture(String name, GUITexture texture) { this.transformMatrix = new Matrix4f();
textures.put(name, texture); this.transformMatrixData = new float[16];
} }
public void start() { public void start() {
@ -122,14 +138,86 @@ public class GuiRenderer {
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount); glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount);
} }
private void addNamePlate(OtherPlayer player) {
GUITexture texture = new GUITexture(generateNameplateImage(player.getUsername()));
playerNamePlates.put(player, texture);
}
private void removeNamePlate(OtherPlayer player) {
GUITexture texture = playerNamePlates.remove(player);
texture.free();
}
private void retainNamePlates(Collection<OtherPlayer> players) {
Set<OtherPlayer> removalSet = new HashSet<>(playerNamePlates.keySet());
removalSet.removeAll(players);
for (OtherPlayer playerToRemove : removalSet) {
removeNamePlate(playerToRemove);
}
}
public void updateNamePlates(Collection<OtherPlayer> players) {
for (OtherPlayer player : players) {
if (!playerNamePlates.containsKey(player)) {
addNamePlate(player);
}
}
retainNamePlates(players);
}
private BufferedImage generateNameplateImage(String username) {
BufferedImage testImg = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D testGraphics = testImg.createGraphics();
testGraphics.setFont(namePlateFont);
int textWidth = testGraphics.getFontMetrics(namePlateFont).stringWidth(username);
int textHeight = testGraphics.getFontMetrics(namePlateFont).getHeight();
int w = textWidth + 20;
int h = textHeight + 10;
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setBackground(new Color(0, 0, 0, 0.5f));
g.clearRect(0, 0, w, h);
g.setColor(Color.WHITE);
g.setFont(namePlateFont);
g.drawString(username, 10, h - 15);
return img;
}
public void drawNameplates(ClientPlayer myPlayer, float[] viewTransformData, float[] perspectiveTransformData) {
shaderProgram.stopUsing();
namePlateShaderProgram.use();
glEnable(GL_DEPTH_TEST);
glActiveTexture(0);
glUniform1i(namePlateTextureSamplerUniform, 0);
glUniformMatrix4fv(namePlateViewTransformUniform, false, viewTransformData);
glUniformMatrix4fv(namePlatePerspectiveTransformUniform, false, perspectiveTransformData);
for (var entry : playerNamePlates.entrySet()) {
OtherPlayer player = entry.getKey();
// Skip rendering far-away nameplates.
if (player.getPosition().distance(myPlayer.getPosition()) > 50) continue;
GUITexture texture = entry.getValue();
float aspectRatio = (float) texture.getHeight() / (float) texture.getWidth();
transformMatrix.identity()
.translate(player.getPosition().x(), player.getPosition().y() + Player.HEIGHT + 1f, player.getPosition().z())
.rotate(myPlayer.getOrientation().x, Camera.UP)
.scale(1f, aspectRatio, 0f)
.get(transformMatrixData);
glUniformMatrix4fv(namePlateTransformUniform, false, transformMatrixData);
glBindTexture(GL_TEXTURE_2D, texture.getTextureId());
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount);
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_DEPTH_TEST);
namePlateShaderProgram.stopUsing();
shaderProgram.use();
}
public void drawNvg(float width, float height, ClientPlayer player) { public void drawNvg(float width, float height, ClientPlayer player) {
nvgBeginFrame(vgId, width, height, width / height); nvgBeginFrame(vgId, width, height, width / height);
nvgSave(vgId); nvgSave(vgId);
nvgFontSize(vgId, 60f);
nvgFontFaceId(vgId, jetbrainsMonoFont);
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
nvgFillColor(vgId, GuiUtils.rgba(1, 0, 0, 1, colorA));
nvgText(vgId, 5, 5, "Hello world!");
drawCrosshair(width, height); drawCrosshair(width, height);
drawHealthBar(width, height, player); drawHealthBar(width, height, player);
@ -150,7 +238,9 @@ public class GuiRenderer {
public void free() { public void free() {
memFree(jetbrainsMonoFontData); memFree(jetbrainsMonoFontData);
nvgDelete(vgId); nvgDelete(vgId);
for (var tex : textures.values()) tex.free(); for (var texture : playerNamePlates.values()) {
texture.free();
}
glDeleteBuffers(vboId); glDeleteBuffers(vboId);
glDeleteVertexArrays(vaoId); glDeleteVertexArrays(vaoId);
shaderProgram.free(); shaderProgram.free();

View File

@ -0,0 +1,10 @@
#version 460 core
in vec2 texturePosition;
out vec4 fragmentColor;
uniform sampler2D guiTexture;
void main() {
fragmentColor = texture(guiTexture, texturePosition);
}

View File

@ -0,0 +1,17 @@
#version 460 core
in vec2 vertexPosition;
uniform mat4 transform;
uniform mat4 viewTransform;
uniform mat4 perspectiveTransform;
out vec2 texturePosition;
void main() {
gl_Position = perspectiveTransform * viewTransform * transform * vec4(vertexPosition, 0.0, 1.0);
texturePosition = vec2(
(vertexPosition.x + 1.0) / 2.0,
1 - (vertexPosition.y + 1.0) / 2.0
);
}

View File

@ -47,4 +47,10 @@ public final class FileUtils {
return buffer; return buffer;
} }
} }
public static InputStream getClasspathResource(String resource) throws IOException {
InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resource);
if (in == null) throw new IOException("Resource not found: " + resource);
return in;
}
} }