diff --git a/assets/Shaders/ui/font/bitmapchar/bitmapchar.fs b/assets/Shaders/ui/font/bitmapchar/bitmapchar.fs index 9b1948c3..d44a0a6c 100644 --- a/assets/Shaders/ui/font/bitmapchar/bitmapchar.fs +++ b/assets/Shaders/ui/font/bitmapchar/bitmapchar.fs @@ -14,5 +14,6 @@ void main(){ textColorModifier.y = 1; textColorModifier.z = 1; } - FragColor = texture(screenTexture, TexCoords) * vec4(textColorModifier.xyz, 1.0); + vec4 sample = texture(screenTexture, TexCoords); + FragColor = vec4(sample.r) * vec4(textColorModifier.xyz, 1.0); } \ No newline at end of file diff --git a/buildNumber.properties b/buildNumber.properties index 41399377..7813d687 100644 --- a/buildNumber.properties +++ b/buildNumber.properties @@ -1,3 +1,3 @@ #maven.buildNumber.plugin properties file -#Wed Nov 20 13:22:48 EST 2024 -buildNumber=392 +#Wed Nov 20 16:01:40 EST 2024 +buildNumber=398 diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 2f825f71..a32883e6 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1090,6 +1090,8 @@ Fix invalid normals from transvoxel rasterizer Design storm engine icon Fix edge-polygon generation for invalid cases Add engine logo to title menu +Use STBttf for font loading/remove dependency on java.awt.fonts +Fix font height lookups in string carousel, text input, and word # TODO diff --git a/pom.xml b/pom.xml index 716ff5ec..d5d223f6 100644 --- a/pom.xml +++ b/pom.xml @@ -7,8 +7,8 @@ jar UTF-8 - 21 - 21 + 17 + 17 3.3.3 1.9.19 1.5.7 diff --git a/src/main/java/electrosphere/client/ui/menu/mainmenu/MenuGeneratorsTitleMenu.java b/src/main/java/electrosphere/client/ui/menu/mainmenu/MenuGeneratorsTitleMenu.java index 959ed6c7..404e90ac 100644 --- a/src/main/java/electrosphere/client/ui/menu/mainmenu/MenuGeneratorsTitleMenu.java +++ b/src/main/java/electrosphere/client/ui/menu/mainmenu/MenuGeneratorsTitleMenu.java @@ -43,31 +43,31 @@ public class MenuGeneratorsTitleMenu { //label (title) - Label titleLabel = Label.createLabel("ORPG"); + Label titleLabel = Label.createLabel("ORPG",3.0f); optionPanel.addChild(titleLabel); //button (multiplayer) - optionPanel.addChild(Button.createButtonCentered("Singleplayer", () -> { + optionPanel.addChild(Button.createButtonCentered("Singleplayer", 2.0f, () -> { WindowUtils.replaceMainMenuContents(MenuGenerators.createWorldSelectMenu()); }).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE)); //button (multiplayer) - optionPanel.addChild(Button.createButtonCentered("Multiplayer", () -> { + optionPanel.addChild(Button.createButtonCentered("Multiplayer", 2.0f, () -> { WindowUtils.replaceMainMenuContents(MenuGenerators.createMultiplayerMenu()); }).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE)); //button (static level) - optionPanel.addChild(Button.createButtonCentered("Level Editor", () -> { + optionPanel.addChild(Button.createButtonCentered("Level Editor", 2.0f, () -> { WindowUtils.replaceMainMenuContents(MenuGeneratorsLevelEditor.createLevelEditorTopMenu()); }).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE)); //button (options) - optionPanel.addChild(Button.createButtonCentered("Options", () -> { + optionPanel.addChild(Button.createButtonCentered("Options", 2.0f, () -> { WindowUtils.replaceMainMenuContents(MenuGenerators.createOptionsMainMenu()); }).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE)); //button (sp debug) - optionPanel.addChild(Button.createButtonCentered("Debug SP Quickstart", () -> { + optionPanel.addChild(Button.createButtonCentered("Debug SP Quickstart", 2.0f, () -> { LoadingThread loadingThread = new LoadingThread(LoadingThreadType.DEBUG_RANDOM_SP_WORLD); Globals.RUN_CLIENT = true; Globals.RUN_SERVER = true; @@ -75,7 +75,7 @@ public class MenuGeneratorsTitleMenu { }).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE)); //button (sp debug) - optionPanel.addChild(Button.createButtonCentered("Load Test Generation Realm", () -> { + optionPanel.addChild(Button.createButtonCentered("Load Test Generation Realm", 2.0f, () -> { LoadingThread loadingThread = new LoadingThread(LoadingThreadType.CHUNK_GENERATION_REALM); Globals.RUN_CLIENT = true; Globals.RUN_SERVER = true; @@ -83,12 +83,12 @@ public class MenuGeneratorsTitleMenu { }).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE)); //button (ui testing) - optionPanel.addChild(Button.createButtonCentered("UI Testing", () -> { + optionPanel.addChild(Button.createButtonCentered("UI Testing", 2.0f, () -> { WindowUtils.replaceMainMenuContents(MenuGeneratorsUITesting.createUITestMenu()); }).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE)); //button (Viewport Test) - optionPanel.addChild(Button.createButtonCentered("Viewport Test", () -> { + optionPanel.addChild(Button.createButtonCentered("Viewport Test", 2.0f, () -> { Globals.threadManager.start(new LoadingThread(LoadingThreadType.LOAD_VIEWPORT)); }).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE)); diff --git a/src/main/java/electrosphere/renderer/RenderUtils.java b/src/main/java/electrosphere/renderer/RenderUtils.java index 3823f00f..3bd3a836 100644 --- a/src/main/java/electrosphere/renderer/RenderUtils.java +++ b/src/main/java/electrosphere/renderer/RenderUtils.java @@ -677,22 +677,22 @@ public class RenderUtils { //texture coords FloatBuffer textureArrayBufferData = BufferUtils.createFloatBuffer(12); textureArrayBufferData.put(0); - textureArrayBufferData.put(0); - - textureArrayBufferData.put(0); - textureArrayBufferData.put(1); - - textureArrayBufferData.put(1); textureArrayBufferData.put(1); textureArrayBufferData.put(0); textureArrayBufferData.put(0); textureArrayBufferData.put(1); + textureArrayBufferData.put(0); + + textureArrayBufferData.put(0); textureArrayBufferData.put(1); textureArrayBufferData.put(1); textureArrayBufferData.put(0); + + textureArrayBufferData.put(1); + textureArrayBufferData.put(1); textureArrayBufferData.flip(); diff --git a/src/main/java/electrosphere/renderer/texture/Texture.java b/src/main/java/electrosphere/renderer/texture/Texture.java index fc53d6ad..bc37eee3 100644 --- a/src/main/java/electrosphere/renderer/texture/Texture.java +++ b/src/main/java/electrosphere/renderer/texture/Texture.java @@ -275,6 +275,44 @@ public class Texture { } } } + + /** + * Generates a texture based on a buffer (for use passing data to gpu) + * @param openGlState The OpenGL state + * @param buffer The buffer of data + * @param width the 'width' of the 'texture' + * @param height the 'height' of the 'texture' + */ + public static Texture createBitmap(OpenGLState openGlState, ByteBuffer buffer, int width, int height){ + Texture rVal = null; + if(!Globals.HEADLESS){ + rVal = new Texture(); + //generate the texture object on gpu + rVal.texturePointer = GL40.glGenTextures(); + Globals.renderingEngine.checkError(); + //bind the new texture + openGlState.glBindTexture(GL_TEXTURE_2D, rVal.getTexturePointer()); + //how are we gonna wrap the texture?? + rVal.setWrap(openGlState, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + rVal.setWrap(openGlState, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + //disable mipmap + rVal.setMinFilter(openGlState, GL_LINEAR); + rVal.setMagFilter(openGlState, GL_LINEAR); + //call if width != height so opengl figures out how to unpack it properly + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + //GL_RED = 32bit r value + //buffer the texture information + rVal.pixelFormat = GL_RED; + rVal.datatype = GL_FLOAT; + rVal.glTexImage2D(openGlState, GL40.GL_RED, width, height, GL40.GL_RED, GL40.GL_UNSIGNED_BYTE, buffer); + //check build status + String errorMessage = RenderingEngine.getErrorInEnglish(Globals.renderingEngine.getError()); + if(errorMessage != null){ + LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Texture Constructor[from bytebuffer]: " + errorMessage)); + } + } + return rVal; + } /** * Binds the texture to unit 0 diff --git a/src/main/java/electrosphere/renderer/ui/elements/BitmapCharacter.java b/src/main/java/electrosphere/renderer/ui/elements/BitmapCharacter.java index b001a636..891174ec 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/BitmapCharacter.java +++ b/src/main/java/electrosphere/renderer/ui/elements/BitmapCharacter.java @@ -10,6 +10,7 @@ import electrosphere.renderer.model.Model; import electrosphere.renderer.ui.elementtypes.DrawableElement; import electrosphere.renderer.ui.events.Event; import electrosphere.renderer.ui.font.Font; + import org.joml.Vector3f; /** @@ -22,6 +23,8 @@ public class BitmapCharacter extends StandardElement implements DrawableElement Vector3f color = new Vector3f(1.0f); Font font; + + float fontSize = 1.0f; /** @@ -33,12 +36,13 @@ public class BitmapCharacter extends StandardElement implements DrawableElement * @param height * @param toDraw */ - public BitmapCharacter(Font font, int width, int height, char toDraw){ + public BitmapCharacter(Font font, int width, int height, float fontSize, char toDraw){ super(); setWidth(width); setHeight(height); this.text = "" + toDraw; this.font = font; + this.fontSize = fontSize; } /** @@ -52,7 +56,7 @@ public class BitmapCharacter extends StandardElement implements DrawableElement this.font = font; Vector3f discreteDims = this.font.getDimensionOfCharacterDiscrete(toDraw); setMinWidth((int)discreteDims.x); - setMinHeight((int)discreteDims.y); + setMinHeight((int)Math.max(discreteDims.y,this.font.getFontHeight())); } @@ -81,20 +85,14 @@ public class BitmapCharacter extends StandardElement implements DrawableElement framebuffer.bind(openGLState); openGLState.glViewport(framebuffer.getWidth(), framebuffer.getHeight()); float ndcX = (float)this.absoluteToFramebuffer(getAbsoluteX(),framebufferPosX)/framebuffer.getWidth(); - float ndcY = (float)this.absoluteToFramebuffer(getAbsoluteY(),framebufferPosY)/framebuffer.getHeight(); + float ndcY = (float)this.absoluteToFramebuffer(getAbsoluteYPlacement(),framebufferPosY)/framebuffer.getHeight(); float ndcWidth = (float)getWidth()/framebuffer.getWidth(); - float ndcHeight = (float)getHeight()/framebuffer.getHeight(); -// float charWidth = ndcWidth/cols; -// float charHeight = ndcHeight/rows; + float ndcHeight = (float)getHeightPlacement()/framebuffer.getHeight(); char toDraw = text.charAt(0); Vector3f characterPosition = new Vector3f(ndcX,ndcY,0); Vector3f characterDimensions = new Vector3f(ndcWidth,ndcHeight,0); Vector3f bitMapPosition = this.font.getPositionOfCharacter(toDraw); Vector3f bitMapDimension = this.font.getDimensionOfCharacter(toDraw); -// bitMapDimension.y = 1; -// System.out.println(bitMapPosition); -// System.out.println(bitMapDimension); -// System.out.println("\n\n"); //load model and try overwriting with font material Model charModel = Globals.assetManager.fetchModel(AssetDataStrings.BITMAP_CHARACTER_MODEL); Material mat = this.font.getMaterial(); @@ -109,6 +107,23 @@ public class BitmapCharacter extends StandardElement implements DrawableElement } } + /** + * Gets the absolute y to use for placement + * @return The absolute y to use for placement + */ + public int getAbsoluteYPlacement(){ + return super.getAbsoluteY() + (int)Math.ceil(this.font.getOffsetY(text.charAt(0)) * this.fontSize); + } + + + /** + * Gets the height to use for placement + * @return The height to use for placement + */ + public int getHeightPlacement(){ + return (int)(Math.ceil(super.getHeight() * this.font.getQuadScalingY(text.charAt(0)))); + } + public boolean handleEvent(Event event){ return true; } diff --git a/src/main/java/electrosphere/renderer/ui/elements/Button.java b/src/main/java/electrosphere/renderer/ui/elements/Button.java index fbf430c9..094216ec 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/Button.java +++ b/src/main/java/electrosphere/renderer/ui/elements/Button.java @@ -119,6 +119,29 @@ public class Button extends StandardContainerElement implements DrawableElement, return rVal; } + /** + * Creates a button that fires a callback when clicked + * @param text The text for the button label + * @param fontSize The size of the font for the label + * @param callback The callback + * @return The button + */ + public static Button createButtonCentered(String text, float fontSize, Runnable callback){ + Button rVal = new Button(); + Label rValLabel = Label.createLabel(text, fontSize); + rValLabel.setText(text); + rVal.addChild(rValLabel); + rVal.setOnClick(new ClickableElement.ClickEventCallback(){public boolean execute(ClickEvent event){ + callback.run(); + if(Globals.virtualAudioSourceManager != null && rVal.audioPathOnClick != null){ + Globals.virtualAudioSourceManager.createUI(rVal.audioPathOnClick); + } + return false; + }}); + rVal.setAlignSelf(YogaAlignment.Center); + return rVal; + } + public boolean getVisible() { return visible; } diff --git a/src/main/java/electrosphere/renderer/ui/elements/Label.java b/src/main/java/electrosphere/renderer/ui/elements/Label.java index 9ecb4da1..f3f77e16 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/Label.java +++ b/src/main/java/electrosphere/renderer/ui/elements/Label.java @@ -43,6 +43,18 @@ public class Label extends StandardContainerElement implements DrawableElement { return rVal; } + /** + * Creates a label element + * @param text the text for the label + * @param fontSize The size of the font + * @return the label element + */ + public static Label createLabel(String text, float fontSize){ + Label rVal = new Label(fontSize); + rVal.setText(text); + return rVal; + } + /** * Simplified constructor * @param fontSize the size of the font (default is 1.0f) @@ -65,13 +77,23 @@ public class Label extends StandardContainerElement implements DrawableElement { for(int i = 0; i < text.length(); i++){ char toDraw = text.charAt(i); Vector3f bitMapDimension = this.font.getDimensionOfCharacterDiscrete(toDraw); - BitmapCharacter newLetter = new BitmapCharacter(this.font,(int)(bitMapDimension.x * fontSize), this.getHeight(), toDraw); + BitmapCharacter newLetter = new BitmapCharacter(this.font,(int)(bitMapDimension.x * fontSize), this.getHeight(), fontSize, toDraw); accumulatingWidth += bitMapDimension.x * fontSize; childList.add(newLetter); Yoga.YGNodeInsertChild(yogaNode, newLetter.getYogaNode(), childList.size() - 1); } Yoga.YGNodeStyleSetWidth(yogaNode, accumulatingWidth); } + + /** + * Sets the size of the font for this label + * @param fontSize The size of the font + */ + public void setFontSize(float fontSize){ + this.fontSize = fontSize; + this.setHeight((int)(font.getFontHeight() * fontSize)); + this.generateLetters(); + } public void setText(String text){ this.text = text; diff --git a/src/main/java/electrosphere/renderer/ui/elements/StringCarousel.java b/src/main/java/electrosphere/renderer/ui/elements/StringCarousel.java index 16104ebc..73003a4b 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/StringCarousel.java +++ b/src/main/java/electrosphere/renderer/ui/elements/StringCarousel.java @@ -58,7 +58,7 @@ public class StringCarousel extends StandardContainerElement implements Drawable super(); this.font = Globals.fontManager.getFont("default"); this.fontSize = fontSize; - Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.imageHeight * fontSize); + Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.getFontHeight() * fontSize); Yoga.YGNodeStyleSetMinWidth(this.yogaNode, 1); } @@ -68,7 +68,7 @@ public class StringCarousel extends StandardContainerElement implements Drawable private StringCarousel(){ super(); this.font = Globals.fontManager.getFont("default"); - Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.imageHeight * fontSize); + Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.getFontHeight() * fontSize); Yoga.YGNodeStyleSetMinWidth(this.yogaNode, 1); } @@ -120,7 +120,7 @@ public class StringCarousel extends StandardContainerElement implements Drawable for(int i = 0; i < textCurrent.length(); i++){ char toDraw = textCurrent.charAt(i); Vector3f bitMapDimension = this.font.getDimensionOfCharacterDiscrete(toDraw); - BitmapCharacter newLetter = new BitmapCharacter(this.font,(int)(bitMapDimension.x * fontSize), this.getHeight(), toDraw); + BitmapCharacter newLetter = new BitmapCharacter(this.font,(int)(bitMapDimension.x * fontSize), this.getHeight(), fontSize, toDraw); accumulatingWidth += bitMapDimension.x * fontSize; addChild(newLetter); } diff --git a/src/main/java/electrosphere/renderer/ui/elements/TextInput.java b/src/main/java/electrosphere/renderer/ui/elements/TextInput.java index a56e5bcf..88cf8ac3 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/TextInput.java +++ b/src/main/java/electrosphere/renderer/ui/elements/TextInput.java @@ -88,7 +88,7 @@ public class TextInput extends StandardContainerElement implements DrawableEleme this.color = new Vector3f(1,1,1); setHeight((int)(font.getFontHeight() * fontSize)); Yoga.YGNodeStyleSetFlexDirection(this.yogaNode, Yoga.YGFlexDirectionRow); - Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.imageHeight * fontSize); + Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.getFontHeight() * fontSize); Yoga.YGNodeStyleSetMinWidth(this.yogaNode, 1); } @@ -100,7 +100,7 @@ public class TextInput extends StandardContainerElement implements DrawableEleme for(int i = 0; i < text.length(); i++){ char toDraw = text.charAt(i); Vector3f bitMapDimension = this.font.getDimensionOfCharacterDiscrete(toDraw); - BitmapCharacter newLetter = new BitmapCharacter(this.font,(int)(bitMapDimension.x * fontSize), this.getHeight(), toDraw); + BitmapCharacter newLetter = new BitmapCharacter(this.font,(int)(bitMapDimension.x * fontSize), this.getHeight(), fontSize, toDraw); newLetter.setColor(color); this.addChild(newLetter); } diff --git a/src/main/java/electrosphere/renderer/ui/font/Font.java b/src/main/java/electrosphere/renderer/ui/font/Font.java index 11c66d09..9d68c631 100644 --- a/src/main/java/electrosphere/renderer/ui/font/Font.java +++ b/src/main/java/electrosphere/renderer/ui/font/Font.java @@ -2,6 +2,8 @@ package electrosphere.renderer.ui.font; import java.util.HashMap; import java.util.List; +import java.util.Map; + import org.joml.Vector3f; import electrosphere.renderer.model.Material; @@ -23,6 +25,8 @@ public class Font { HashMap positionMap = new HashMap(); //the map of character->dimension of the character in pixels HashMap dimensionMap = new HashMap(); + + Map glyphMap = new HashMap(); /** * Creates the font object @@ -53,6 +57,8 @@ public class Font { public int startY; public int width; public int height; + public int offsetY = 0; + public float quadScalingY = 1; } /** @@ -103,13 +109,45 @@ public class Font { } return dimension; } + + /** + * Gets the offset y of the character + * @param c The character + * @return The offset y + */ + public int getOffsetY(Character c){ + int rVal = 0; + if(glyphMap.containsKey(c)){ + rVal = glyphMap.get(c).offsetY; + } + return rVal; + } + + /** + * Gets the quad y scaling of the character + * @param c The character + * @return The scaling + */ + public float getQuadScalingY(Character c){ + float rVal = 1.0f; + if(glyphMap.containsKey(c)){ + rVal = glyphMap.get(c).quadScalingY; + } + return rVal; + } /** * Gets the height of the font * @return the height */ public int getFontHeight(){ - return imageHeight; + int maxHeight = 0; + for(Glyph glyph : this.glyphs){ + if(glyph.height > maxHeight){ + maxHeight = glyph.height; + } + } + return maxHeight; } @@ -129,6 +167,10 @@ public class Font { Vector3f dimension = new Vector3f(glyph.width,glyph.height,0); dimensionMap.put(charVal,dimension); } + //fill glyph map + for(Glyph glyph: glyphs){ + this.glyphMap.put(glyph.symbol.charAt(0),glyph); + } } /** diff --git a/src/main/java/electrosphere/renderer/ui/font/FontManager.java b/src/main/java/electrosphere/renderer/ui/font/FontManager.java index 1fc7f0f4..816538d6 100644 --- a/src/main/java/electrosphere/renderer/ui/font/FontManager.java +++ b/src/main/java/electrosphere/renderer/ui/font/FontManager.java @@ -34,22 +34,24 @@ public class FontManager { * Loads fonts at engine startup during the init graphics resource phase */ public void loadFonts(){ - java.awt.Font font = null; - try { - font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, FileUtils.getAssetFileAsStream("Fonts/Tuffy_Bold.ttf")); - font = font.deriveFont(24f); - } catch (FontFormatException e) { - LoggerInterface.loggerEngine.ERROR("Failed to load a font!", e); - } catch (IOException e) { - LoggerInterface.loggerEngine.ERROR("Failed to load a font!", e); - } - if(font==null){ - font = new java.awt.Font(java.awt.Font.MONOSPACED, java.awt.Font.PLAIN, 16); - } - if(font!=null){ - defaultFont = FontUtils.loadFont(Globals.renderingEngine.getOpenGLState(), font, true); - fontMap.put("default",defaultFont); - } + // java.awt.Font font = null; + // try { + // font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, FileUtils.getAssetFileAsStream("Fonts/Tuffy_Bold.ttf")); + // font = font.deriveFont(24f); + // } catch (FontFormatException e) { + // LoggerInterface.loggerEngine.ERROR("Failed to load a font!", e); + // } catch (IOException e) { + // LoggerInterface.loggerEngine.ERROR("Failed to load a font!", e); + // } + // if(font==null){ + // font = new java.awt.Font(java.awt.Font.MONOSPACED, java.awt.Font.PLAIN, 16); + // } + // if(font != null){ + // defaultFont = FontUtils.loadFont(Globals.renderingEngine.getOpenGLState(), font, true); + // fontMap.put("default",defaultFont); + // } + defaultFont = FontUtils.loadTTF(Globals.renderingEngine.getOpenGLState(), "Fonts/Tuffy_Bold.ttf"); + fontMap.put("default",defaultFont); } } diff --git a/src/main/java/electrosphere/renderer/ui/font/FontUtils.java b/src/main/java/electrosphere/renderer/ui/font/FontUtils.java index ad207027..9f0563f7 100644 --- a/src/main/java/electrosphere/renderer/ui/font/FontUtils.java +++ b/src/main/java/electrosphere/renderer/ui/font/FontUtils.java @@ -1,10 +1,24 @@ package electrosphere.renderer.ui.font; +import electrosphere.logger.LoggerInterface; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.model.Material; import electrosphere.renderer.texture.Texture; +import electrosphere.util.FileUtils; + +import org.lwjgl.BufferUtils; +import org.lwjgl.stb.STBTTAlignedQuad; +import org.lwjgl.stb.STBTTFontinfo; +import org.lwjgl.stb.STBTTPackContext; +import org.lwjgl.stb.STBTTPackedchar; +import org.lwjgl.stb.STBTruetype; +import org.lwjgl.system.MemoryStack; import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; import java.awt.RenderingHints; import java.awt.FontMetrics; import java.awt.Graphics2D; @@ -17,6 +31,29 @@ import java.util.List; */ public class FontUtils { + public static final int TTF_CHAR_WIDTH = 32; + public static final int TTF_CHAR_HEIGHT = 24; + static final char FIRST_CHAR_TO_BAKE = 32; + static final char LAST_CHAR_TO_BAKE = 127; + static final int CHAR_COUNT = LAST_CHAR_TO_BAKE - FIRST_CHAR_TO_BAKE; + static final int TTF_BITMAP_WIDTH = 512; + static final int TTF_BITMAP_HEIGHT = 512; + static final int SPACE_WIDTH = 8; + + /** + * Manual offset to align them + */ + static final int MANUAL_OFFSET = -2; + + /** + * The factor to adjust the descent by. Just intuiting this from how the data looks, not guaranteed to be correct + */ + static final int DESCENT_DIVISOR = 100; + + static float[] scale = new float[]{ + TTF_CHAR_HEIGHT, + }; + /** * Renders a single character to a buffered image @@ -157,5 +194,136 @@ public class FontUtils { return rVal; } + /** + * Loads the TTF font + * @param openGLState The opengl state + * @param path The path to the ttf font file + * @return The font if it loaded successfully, null otherwise + */ + protected static Font loadTTF(OpenGLState openGLState, String path){ + + //used for cosntructing the font object to return + List glyphs = new LinkedList(); + Material uiMat = new Material(); + + //read the file + ByteBuffer fileContent = null; + try { + fileContent = FileUtils.getAssetFileAsByteBuffer(path); + } catch (IOException e) { + LoggerInterface.loggerFileIO.ERROR("Failed to read font file", e); + } + if(fileContent == null){ + return null; + } + + //load info about the file + STBTTFontinfo info = STBTTFontinfo.create(); + if(!STBTruetype.stbtt_InitFont(info, fileContent)){ + throw new Error("Failed to init font info!"); + } + + int descent = 0; + try(MemoryStack stack = MemoryStack.stackPush()){ + IntBuffer ascentBuff = stack.mallocInt(1); + IntBuffer descentBuff = stack.mallocInt(1); + IntBuffer lineGapBuff = stack.mallocInt(1); + + //get data on the font + STBTruetype.stbtt_GetFontVMetrics(info, ascentBuff, descentBuff, lineGapBuff); + + // int ascent = ascentBuff.get(0); + descent = descentBuff.get(0); + // int lineGap = lineGapBuff.get(0); + } + + try(STBTTPackContext packContext = STBTTPackContext.malloc()){ + + //get char data + STBTTPackedchar.Buffer charData = STBTTPackedchar.malloc(6 * 128); + ByteBuffer bakedTextureData = BufferUtils.createByteBuffer(TTF_BITMAP_WIDTH * TTF_BITMAP_HEIGHT); + STBTruetype.stbtt_PackBegin(packContext, bakedTextureData, TTF_BITMAP_WIDTH, TTF_BITMAP_HEIGHT, 0, 1); + + //make image + for(int i = 0; i < scale.length; i++){ + int p = (i * 3 + 0) * CHAR_COUNT + FIRST_CHAR_TO_BAKE; + charData.limit(p + 95); + charData.position(p); + STBTruetype.stbtt_PackSetOversampling(packContext, 1, 1); + STBTruetype.stbtt_PackFontRange(packContext, fileContent, 0, scale[i], FIRST_CHAR_TO_BAKE, charData); + + p = (i * 3 + 1) * CHAR_COUNT + FIRST_CHAR_TO_BAKE; + charData.limit(p + 95); + charData.position(p); + STBTruetype.stbtt_PackSetOversampling(packContext, 2, 2); + STBTruetype.stbtt_PackFontRange(packContext, fileContent, 0, scale[i], FIRST_CHAR_TO_BAKE, charData); + + p = (i * 3 + 2) * CHAR_COUNT + FIRST_CHAR_TO_BAKE; + charData.limit(p + 95); + charData.position(p); + STBTruetype.stbtt_PackSetOversampling(packContext, 3, 1); + STBTruetype.stbtt_PackFontRange(packContext, fileContent, 0, scale[i], FIRST_CHAR_TO_BAKE, charData); + } + charData.clear(); + STBTruetype.stbtt_PackEnd(packContext); + + //create the bitmap image off of the raw texture data + Texture texture = Texture.createBitmap(openGLState, bakedTextureData, TTF_BITMAP_WIDTH, TTF_BITMAP_HEIGHT); + uiMat.setTexturePointer(texture.getTexturePointer()); + + //parse the glyphs + try(MemoryStack stack = MemoryStack.stackPush()){ + STBTTAlignedQuad quad = STBTTAlignedQuad.malloc(stack); + FloatBuffer xPos = stack.floats(0.0f); + FloatBuffer yPos = stack.floats(0.0f); + for (int i = FIRST_CHAR_TO_BAKE; i < LAST_CHAR_TO_BAKE; i++) { + xPos.put(0,0); + yPos.put(0,0); + if (i == 127) { + //skip del character command + continue; + } + STBTruetype.stbtt_GetPackedQuad(charData, TTF_BITMAP_WIDTH, TTF_BITMAP_HEIGHT, i, xPos, yPos, quad, false); + // float charX = xPos.get(0); + // float charY = yPos.get(0); + + // float x0 = quad.x0(); + // float x1 = quad.x1(); + // float y0 = quad.y0(); + // float y1 = quad.y1(); + // float s0 = quad.s0(); + // float s1 = quad.s1(); + // float t0 = quad.t0(); + // float t1 = quad.t1(); + + + //create glyph and push it into the array + Font.Glyph glyph = new Font.Glyph(); + glyph.width = (int)((quad.s1() - quad.s0()) * TTF_BITMAP_WIDTH); + glyph.height = (int)Math.ceil((quad.t1() - quad.t0()) * TTF_BITMAP_HEIGHT); + glyph.startX = (int)(quad.s0() * TTF_BITMAP_WIDTH); + glyph.startY = (int)Math.ceil((quad.t0() * TTF_BITMAP_HEIGHT)); + glyph.offsetY = (int)(TTF_CHAR_HEIGHT + quad.y0()) + (descent / DESCENT_DIVISOR) + MANUAL_OFFSET; + glyph.quadScalingY = (quad.y1() - quad.y0()) / (float)TTF_CHAR_HEIGHT; + glyph.symbol = (char)i + ""; + if(i == 32){ + glyph.width = SPACE_WIDTH; + } + glyphs.add(glyph); + + // if(i == 110 || i == 100 || i == 76 || i == 82){ + // System.out.println((char)i + ""); + // } + + } + } + } + + //construct final font object and return + Font rVal = new Font(uiMat,glyphs,TTF_BITMAP_WIDTH,TTF_BITMAP_HEIGHT); + rVal.process(); + + return rVal; + } } diff --git a/src/main/java/electrosphere/util/FileUtils.java b/src/main/java/electrosphere/util/FileUtils.java index d1bf60e0..eee6622d 100644 --- a/src/main/java/electrosphere/util/FileUtils.java +++ b/src/main/java/electrosphere/util/FileUtils.java @@ -21,6 +21,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -32,6 +34,8 @@ import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; +import org.lwjgl.BufferUtils; + /** * Utilities for dealing with files */ @@ -186,6 +190,25 @@ public class FileUtils { return targetFile; } + /** + * Gets an assets file as a byte buffer + * @param pathName The relative path in the assets folder + * @return The byte buffer containing the contents of the asset file + * @throws IOException Thrown if there are any io issues + */ + public static ByteBuffer getAssetFileAsByteBuffer(String pathName) throws IOException{ + String sanitizedFilePath = sanitizeFilePath(pathName); + File targetFile = new File("./assets" + sanitizedFilePath); + ByteBuffer buff = null; + try(SeekableByteChannel byteChannel = Files.newByteChannel(targetFile.toPath())){ + buff = BufferUtils.createByteBuffer((int)(byteChannel.size() + 1)); + while(byteChannel.read(buff) != -1){ + } + buff.flip(); + } + return buff; + } + /** * Gets a cache file * @param pathName The relative path in the cache folder diff --git a/src/test/java/electrosphere/renderer/ui/elements/BitmapCharacterTests.java b/src/test/java/electrosphere/renderer/ui/elements/BitmapCharacterTests.java index 00c28b13..fa3572a0 100644 --- a/src/test/java/electrosphere/renderer/ui/elements/BitmapCharacterTests.java +++ b/src/test/java/electrosphere/renderer/ui/elements/BitmapCharacterTests.java @@ -14,7 +14,7 @@ public class BitmapCharacterTests extends UITestTemplate { public void test_Create(){ //setup this.setupBlankView(); - BitmapCharacter el = new BitmapCharacter(Globals.fontManager.getFont("default"), 16, 24, 'A'); + BitmapCharacter el = new BitmapCharacter(Globals.fontManager.getFont("default"), 16, 24, 1.0f, 'A'); WindowUtils.replaceMainMenuContents(el); diff --git a/test/java/renderer/ui/elements/bitmapchar1.png b/test/java/renderer/ui/elements/bitmapchar1.png index cea8d0d5..1845c2dd 100644 Binary files a/test/java/renderer/ui/elements/bitmapchar1.png and b/test/java/renderer/ui/elements/bitmapchar1.png differ diff --git a/test/java/renderer/ui/elements/button1.png b/test/java/renderer/ui/elements/button1.png index c9ab7b01..933e2fec 100644 Binary files a/test/java/renderer/ui/elements/button1.png and b/test/java/renderer/ui/elements/button1.png differ diff --git a/test/java/renderer/ui/elements/label1.png b/test/java/renderer/ui/elements/label1.png index b0a8fc8a..a3895c22 100644 Binary files a/test/java/renderer/ui/elements/label1.png and b/test/java/renderer/ui/elements/label1.png differ diff --git a/test/java/renderer/ui/elements/stringcarousel1.png b/test/java/renderer/ui/elements/stringcarousel1.png index 2f2c72a6..eaee23ea 100644 Binary files a/test/java/renderer/ui/elements/stringcarousel1.png and b/test/java/renderer/ui/elements/stringcarousel1.png differ diff --git a/test/java/renderer/ui/elements/textbox1.png b/test/java/renderer/ui/elements/textbox1.png index 0c54636d..41a86053 100644 Binary files a/test/java/renderer/ui/elements/textbox1.png and b/test/java/renderer/ui/elements/textbox1.png differ diff --git a/test/java/renderer/ui/elements/textinput1.png b/test/java/renderer/ui/elements/textinput1.png index ae6d9f86..458f5507 100644 Binary files a/test/java/renderer/ui/elements/textinput1.png and b/test/java/renderer/ui/elements/textinput1.png differ diff --git a/test/java/renderer/ui/elements/word1.png b/test/java/renderer/ui/elements/word1.png index 6d7b716a..387a6f91 100644 Binary files a/test/java/renderer/ui/elements/word1.png and b/test/java/renderer/ui/elements/word1.png differ diff --git a/test/java/renderer/ui/test_Screencapture_Match.png b/test/java/renderer/ui/test_Screencapture_Match.png index 04b65f77..9ef61b5e 100644 Binary files a/test/java/renderer/ui/test_Screencapture_Match.png and b/test/java/renderer/ui/test_Screencapture_Match.png differ