diff --git a/GameEngine/src/main/java/gameEngine/entites/gameComponents/TextMesh.java b/GameEngine/src/main/java/gameEngine/entites/gameComponents/TextMesh.java new file mode 100644 index 0000000..4f19b3d --- /dev/null +++ b/GameEngine/src/main/java/gameEngine/entites/gameComponents/TextMesh.java @@ -0,0 +1,199 @@ +package gameEngine.entites.gameComponents; + +import gameEngine.entites.Entity; +import gameEngine.views.*; + +import javax.swing.*; + +import java.util.Map; + +import static java.awt.Font.*; +import static org.lwjgl.opengl.GL11.*; + +public class TextMesh extends GameComponent{ + + private Texture fontTexture; // スプライトのテクスチャを保持 + private Map fontGlyphs; + private int fontHeight; + private Entity parent; // 親のオブジェクトを持つ + public String text; + + private int spriteWidth = 512; // スプライト幅(今後指定可能にする) + private int spriteHeight = 256; // スプライト高さ(今後指定可能にする) + + + // コンストラクタ + public TextMesh(Entity parent, String text) { + this.parent = parent; + this.text = text; + Font font = new Font(new java.awt.Font(SANS_SERIF, PLAIN, 64),true); + fontTexture = font.getTexture(); + fontGlyphs = font.getGlyphs(); + fontHeight = font.getFontHeight(); + } + + + @Override + public GameComponent copy() { + return new TextMesh(this.parent, this.text); + } + + @Override + public void init() { + renderText(); + } + + @Override + public void update() { + Integer id = fontTexture.getId(); + if (id == null) { + fontTexture.init(); + } + renderText(); // テキストの描画 + } + + public void renderText() { + // Switch to 2D rendering mode + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, Window.get().width, Window.get().height, 0, -1000, 1000); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // Bind the font texture for text rendering + glBindTexture(GL_TEXTURE_2D, fontTexture.getId()); + + // Retrieve parent GameObject's transform data + float x = parent.transform.position.x; + float y = parent.transform.position.y; + float z = parent.transform.position.z; + float rotationX = parent.transform.rotation.x; + float rotationY = parent.transform.rotation.y; + float rotationZ = parent.transform.rotation.z; + float scaleX = parent.transform.scale.x; + float scaleY = parent.transform.scale.y; + float scaleZ = parent.transform.scale.z; + + float zScale = z >= 0 ? 1.0f + (z * 0.1f) : 1.0f / (1.0f + Math.abs(z) * 0.1f); + + glTranslatef(x + spriteWidth / 2.0f, y + spriteHeight / 2.0f, z); + glRotatef(rotationX, 1, 0, 0); + glRotatef(rotationY, 0, 1, 0); + glRotatef(rotationZ, 0, 0, 1); + glScalef(scaleX * zScale, scaleY * zScale, scaleZ); + glTranslatef(-(x + spriteWidth / 2.0f), -(y + spriteHeight / 2.0f), z); + + glEnable(GL_TEXTURE_2D); + glColor4f(1f, 1f, 1f, 1f); + + // Text drawing logic + float drawX = x; + float drawY = y; + int textHeight = getHeight(text); + if (textHeight > fontHeight) { + drawY += textHeight - fontHeight; + } + + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (ch == '\n') { + // Handle new line + drawY -= fontHeight; + drawX = x; + continue; + } + if (ch == '\r') { + continue; + } + + Glyph g = fontGlyphs.get(ch); + if (g != null) { + // Render each character as a textured quad + glBegin(GL_QUADS); + glTexCoord2f(g.x / (float) fontTexture.getWidth(), g.y / (float) fontTexture.getHeight()); + glVertex3f(drawX, drawY, z); + + glTexCoord2f((g.x + g.width) / (float) fontTexture.getWidth(), g.y / (float) fontTexture.getHeight()); + glVertex3f(drawX + g.width, drawY, z); + + glTexCoord2f((g.x + g.width) / (float) fontTexture.getWidth(), (g.y + g.height) / (float) fontTexture.getHeight()); + glVertex3f(drawX + g.width, drawY + g.height, z); + + glTexCoord2f(g.x / (float) fontTexture.getWidth(), (g.y + g.height) / (float) fontTexture.getHeight()); + glVertex3f(drawX, drawY + g.height, z); + glEnd(); + + drawX += g.width; + } + } + + // Reset OpenGL states + glDisable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + + // Restore the previous matrix state + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + } + + /** + * Gets the width of the specified text. + * + * @param text The text + * @return Width of text + */ + public int getWidth(CharSequence text) { + int width = 0; + int lineWidth = 0; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\n') { + /* Line end, set width to maximum from line width and stored + * width */ + width = Math.max(width, lineWidth); + lineWidth = 0; + continue; + } + if (c == '\r') { + /* Carriage return, just skip it */ + continue; + } + Glyph g = fontGlyphs.get(c); + lineWidth += g.width; + } + width = Math.max(width, lineWidth); + return width; + } + + /** + * Gets the height of the specified text. + * + * @param text The text + * @return Height of text + */ + public int getHeight(CharSequence text) { + int height = 0; + int lineHeight = 0; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\n') { + /* Line end, add line height to stored height */ + height += lineHeight; + lineHeight = 0; + continue; + } + if (c == '\r') { + /* Carriage return, just skip it */ + continue; + } + Glyph g = fontGlyphs.get(c); + lineHeight = Math.max(lineHeight, g.height); + } + height += lineHeight; + return height; + } +} diff --git a/GameEngine/src/main/java/gameEngine/scenes/Scene.java b/GameEngine/src/main/java/gameEngine/scenes/Scene.java index 215f8e0..dc414bd 100644 --- a/GameEngine/src/main/java/gameEngine/scenes/Scene.java +++ b/GameEngine/src/main/java/gameEngine/scenes/Scene.java @@ -56,7 +56,8 @@ String newId = Integer.toString(entitiesLength); GameObject newGameObject = new GameObject(newId); addEntity(newId, newGameObject); - newGameObject.addComponent(new Mesh(newGameObject, Mesh.MeshType.SPRITE, "GameEngine/resources/test.png")); + //newGameObject.addComponent(new Mesh(newGameObject, Mesh.MeshType.SPRITE, "GameEngine/resources/test.png")); + newGameObject.addComponent(new TextMesh(newGameObject, "Hello World")); newGameObject.setName("NewEntity" + newId); }); } diff --git a/GameEngine/src/main/java/gameEngine/views/Font.java b/GameEngine/src/main/java/gameEngine/views/Font.java new file mode 100644 index 0000000..467a9a3 --- /dev/null +++ b/GameEngine/src/main/java/gameEngine/views/Font.java @@ -0,0 +1,190 @@ +package gameEngine.views; + +import org.lwjgl.BufferUtils; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +public class Font { + + /** + * Contains the glyphs for each char. + */ + private final Map glyphs; + + private Texture texture; + + private int fontHeight; + + public Map getGlyphs(){ + return glyphs; + } + public Texture getTexture() { + return texture; + } + public int getFontHeight(){ + return fontHeight; + } + + + /** + * Creates a font from an AWT Font. + * + * @param font The AWT Font + * @param antiAlias Wheter the font should be antialiased or not + */ + public Font(java.awt.Font font, boolean antiAlias) { + glyphs = new HashMap<>(); + createFontTexture(font, antiAlias); + } + + private void createFontTexture(java.awt.Font font, boolean antiAlias) { + texture = new Texture(); + /* Loop through the characters to get charWidth and charHeight */ + int imageWidth = 0; + int imageHeight = 0; + + /* Start at char #32, because ASCII 0 to 31 are just control codes */ + for (int i = 32; i < 256; i++) { + if (i == 127) { + /* ASCII 127 is the DEL control code, so we can skip it */ + continue; + } + char c = (char) i; + BufferedImage ch = createCharImage(font, c, antiAlias); + if (ch == null) { + /* If char image is null that font does not contain the char */ + continue; + } + + imageWidth += ch.getWidth(); + imageHeight = Math.max(imageHeight, ch.getHeight()); + } + + fontHeight = imageHeight; + + /* Image for the texture */ + BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + + int x = 0; + + /* Create image for the standard chars, again we omit ASCII 0 to 31 + * because they are just control codes */ + for (int i = 32; i < 256; i++) { + if (i == 127) { + /* ASCII 127 is the DEL control code, so we can skip it */ + continue; + } + char c = (char) i; + BufferedImage charImage = createCharImage(font, c, antiAlias); + if (charImage == null) { + /* If char image is null that font does not contain the char */ + continue; + } + + int charWidth = charImage.getWidth(); + int charHeight = charImage.getHeight(); + + /* Create glyph and draw char on image */ + Glyph ch = new Glyph(charWidth, charHeight, x, image.getHeight() - charHeight, 0f); + g.drawImage(charImage, x, 0, null); + x += ch.width; + glyphs.put(c, ch); + } + + /* Flip image Horizontal to get the origin to bottom left */ + /*AffineTransform transform = AffineTransform.getScaleInstance(1f, -1f); + transform.translate(0, -image.getHeight()); + AffineTransformOp operation = new AffineTransformOp(transform, + AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + image = operation.filter(image, null);*/ + + /* Get charWidth and charHeight of image */ + int width = image.getWidth(); + int height = image.getHeight(); + + /* Get pixel data of image */ + int[] pixelsRaw = new int[width * height]; + image.getRGB(0, 0, width, height, pixelsRaw, 0, width); + + /* Put pixel data into a ByteBuffer */ + ByteBuffer pixels = BufferUtils.createByteBuffer(width * height * 4); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + /* Pixel as RGBA: 0xAARRGGBB */ + int pixel = pixelsRaw[i * width + j]; + /* Red component 0xAARRGGBB >> 16 = 0x0000AARR */ + pixels.put((byte) ((pixel >> 16) & 0xFF)); + /* Green component 0xAARRGGBB >> 8 = 0x00AARRGG */ + pixels.put((byte) ((pixel >> 8) & 0xFF)); + /* Blue component 0xAARRGGBB >> 0 = 0xAARRGGBB */ + pixels.put((byte) (pixel & 0xFF)); + /* Alpha component 0xAARRGGBB >> 24 = 0x000000AA */ + pixels.put((byte) ((pixel >> 24) & 0xFF)); + } + } + /* Do not forget to flip the buffer! */ + pixels.flip(); + + texture.setTextureData(width, height, pixels); + texture.init(); + } + + /** + * Creates a char image from specified AWT font and char. + * + * @param font The AWT font + * @param c The char + * @param antiAlias Wheter the char should be antialiased or not + * @return Char image + */ + private BufferedImage createCharImage(java.awt.Font font, char c, boolean antiAlias) { + /* Creating temporary image to extract character size */ + BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + if (antiAlias) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + g.setFont(font); + FontMetrics metrics = g.getFontMetrics(); + g.dispose(); + + /* Get char charWidth and charHeight */ + int charWidth = metrics.charWidth(c); + int charHeight = metrics.getHeight(); + + /* Check if charWidth is 0 */ + if (charWidth == 0) { + return null; + } + + /* Create image for holding the char */ + image = new BufferedImage(charWidth, charHeight, BufferedImage.TYPE_INT_ARGB); + g = image.createGraphics(); + if (antiAlias) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + g.setFont(font); + g.setPaint(java.awt.Color.WHITE); + g.drawString(String.valueOf(c), 0, metrics.getAscent()); + g.dispose(); + return image; + } + + + + + + + public void delete() { + texture.delete(); + } + +} diff --git a/GameEngine/src/main/java/gameEngine/views/Glyph.java b/GameEngine/src/main/java/gameEngine/views/Glyph.java new file mode 100644 index 0000000..279a172 --- /dev/null +++ b/GameEngine/src/main/java/gameEngine/views/Glyph.java @@ -0,0 +1,33 @@ +package gameEngine.views; + +/** + * This class represents a font glyph. + * + * @author Heiko Brumme + */ +public class Glyph { + + public final int width; + public final int height; + public final int x; + public final int y; + public final float advance; + + /** + * Creates a font Glyph. + * + * @param width Width of the Glyph + * @param height Height of the Glyph + * @param x X coordinate on the font texture + * @param y Y coordinate on the font texture + * @param advance Advance width + */ + public Glyph(int width, int height, int x, int y, float advance) { + this.width = width; + this.height = height; + this.x = x; + this.y = y; + this.advance = advance; + } + +} \ No newline at end of file diff --git a/GameEngine/src/main/java/gameEngine/views/Texture.java b/GameEngine/src/main/java/gameEngine/views/Texture.java index 2a21dc0..03426a6 100644 --- a/GameEngine/src/main/java/gameEngine/views/Texture.java +++ b/GameEngine/src/main/java/gameEngine/views/Texture.java @@ -22,6 +22,15 @@ private int height; // 読み込んだ画像の高さ private ByteBuffer pixels; + public Texture() { + } + + public void setTextureData(int width, int height, ByteBuffer buffer) { + this.width = width; + this.height = height; + this.pixels = buffer; + } + public Texture(String path) { BufferedImage bi; try { @@ -59,8 +68,7 @@ } } - public void init(){ - + public void init() { // 新しいテクスチャIDを生成 id = glGenTextures();