Java tutorial
/** ** This file is part of the project https://github.com/toss-dev/VoxelEngine ** ** License is available here: https://raw.githubusercontent.com/toss-dev/VoxelEngine/master/LICENSE.md ** ** PEREIRA Romain ** 4-----7 ** /| /| ** 0-----3 | ** | 5___|_6 ** |/ | / ** 1-----2 */ package com.grillecube.client.renderer.gui.font; import java.util.ArrayList; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL15; import com.grillecube.client.opengl.GLH; import com.grillecube.client.opengl.GLVertexArray; import com.grillecube.client.opengl.GLVertexBuffer; import com.grillecube.common.maths.Matrix4f; import com.grillecube.common.maths.Vector2f; import com.grillecube.common.maths.Vector3f; import com.grillecube.common.maths.Vector4f; /** * * every coordinates are here relative to opengl screen space * * @author Romain * */ public class FontModel { /** opengl IDs */ private GLVertexArray vao; private GLVertexBuffer vbo; private int vertexCount; /** model data */ private Vector3f pos; private Vector3f scale; private Vector3f rot; private Vector3f rotCenter; private Font font; /** status */ public static final int STATE_INITIALIZED = 1; public static final int STATE_TEXT_UP_TO_DATE = 2; private int state; /** transf transfMatrix */ private Matrix4f transfMatrix; /** text */ private String text; private ArrayList<FontChar> textChars; /** parameters */ private Vector4f color; private float borderWidth; private float borderEdge; private Vector2f outlineOffset; private Vector3f outlineColor; /** text width and height */ private float textWidth; private float textHeight; private int lineCount; private float aspect; private float verticalSizePpx; private float horizontalSizePpx; public FontModel(Font font) { this.state = 0; this.aspect = 1.0f; this.text = new String(); this.textChars = new ArrayList<FontChar>(); this.font = font; this.transfMatrix = new Matrix4f(); this.pos = new Vector3f(); this.rot = new Vector3f(); this.rotCenter = new Vector3f(); this.scale = new Vector3f(); this.set(0, 0, 0, 1.0f, 1.0f, 1.0f, 0, 0, 0, 0, 0, 0); this.color = new Vector4f(1.0f, 0.0f, 0.0f, 1.0f); this.borderWidth = 0.0f; this.borderEdge = 0.5f; this.outlineColor = new Vector3f(0, 0, 0); this.outlineOffset = new Vector2f(0, 0); } public final void initialize() { if (this.hasState(STATE_INITIALIZED)) { return; } this.setState(STATE_INITIALIZED); this.vao = GLH.glhGenVAO(); this.vbo = GLH.glhGenVBO(); this.vao.bind(); { this.vbo.bind(GL15.GL_ARRAY_BUFFER); this.vao.setAttribute(0, 3, GL11.GL_FLOAT, false, (3 + 2 + 4) * 4, 0); this.vao.setAttribute(1, 2, GL11.GL_FLOAT, false, (3 + 2 + 4) * 4, 3 * 4); this.vao.setAttribute(2, 4, GL11.GL_FLOAT, false, (3 + 2 + 4) * 4, (3 + 2) * 4); // this.vbo.unbind(GL15.GL_ARRAY_BUFFER); this.vao.enableAttribute(0); this.vao.enableAttribute(1); this.vao.enableAttribute(2); } // this.vao.unbind(); } /** destroy the model */ public void deinitialize() { if (this.hasState(FontModel.STATE_INITIALIZED)) { GLH.glhDeleteObject(this.vao); GLH.glhDeleteObject(this.vbo); this.unsetState(FontModel.STATE_INITIALIZED); } } /** add a string to the model */ public void addText(String str) { this.setText(this.text == null ? str : this.text + str); } /** rebuild the mesh depending on the given string */ public final void setText(String str) { this.text = str; this.updateFontChars(); this.requestUpdate(); } private final void updateFontChars() { this.textChars.clear(); if (this.font == null || this.text == null || this.text.length() == 0) { this.textWidth = 0; this.textHeight = 0; return; } int length = this.text.length(); float maxwidth = 0; float width = 0; int lineCount = 1; for (int i = 0; i < length; i++) { FontChar fchar = this.font.getFile().getCharData(this.text.charAt(i)); this.textChars.add(fchar); if (this.text.charAt(i) == '\n') { if (maxwidth < width) { maxwidth = width; } width = 0; ++lineCount; continue; } width += fchar.xadvance; } this.textWidth = (width > maxwidth ? width : maxwidth) + this.textChars.get(0).xoffset; this.textHeight = lineCount * FontFile.LINEHEIGHT; this.lineCount = lineCount; } public final int getLineCount() { return (this.lineCount); } public final void setFont(Font font) { this.font = font; this.updateSizePpx(); this.updateFontChars(); this.requestUpdate(); } private void updateSizePpx() { this.verticalSizePpx = FontFile.LINEHEIGHT / (float) this.getFont().getFile().getLineHeight(); this.horizontalSizePpx = this.verticalSizePpx / this.aspect; } /** set the font color */ public final void setFontColor(float r, float g, float b, float a) { this.color.set(r, g, b, a); this.requestUpdate(); } /** update FontModel GLVertexBuffer vertices depending on 'this.text' */ private final void updateText() { float[] vertices = this.generateFontBuffer(); if (!this.hasState(FontModel.STATE_INITIALIZED)) { this.initialize(); } this.vbo.bind(GL15.GL_ARRAY_BUFFER); if (vertices != null) { this.vbo.bufferData(GL15.GL_ARRAY_BUFFER, vertices, GL15.GL_STATIC_DRAW); this.vertexCount = vertices.length / 5; } else { this.vbo.bufferSize(GL15.GL_ARRAY_BUFFER, 0, GL15.GL_STATIC_DRAW); this.vertexCount = 0; } this.setState(FontModel.STATE_TEXT_UP_TO_DATE); } /** each char is a quad (4 vertex) of 5 floats (pos + uv) */ private final float[] generateFontBuffer() { if (this.textChars == null || this.textChars.size() == 0 || this.font == null) { return (null); } int size = this.textChars.size(); Vector4f color = this.color; float[] vertices = new float[size * (6 * (3 + 2 + 4))]; float posx = 0; float posy = FontFile.LINEHEIGHT * this.lineCount; // float posy = 0; float posz = 0; int index = 0; for (int i = 0; i < size; i++) { FontChar fchar = this.textChars.get(i); if (fchar == null) { continue; } if (this.text.charAt(i) == '\n') { posy = posy - FontFile.LINEHEIGHT; posx = 0; continue; } float xsize = fchar.width * this.horizontalSizePpx; float ysize = fchar.height * this.verticalSizePpx; float xoffset = fchar.xoffset * this.horizontalSizePpx; float yoffset = fchar.yoffset * this.verticalSizePpx; // first vertex vertices[index++] = posx + xoffset; vertices[index++] = posy - yoffset; vertices[index++] = posz; vertices[index++] = fchar.uvx; vertices[index++] = fchar.uvy; vertices[index++] = color.x; vertices[index++] = color.y; vertices[index++] = color.z; vertices[index++] = color.w; // second vertex vertices[index++] = posx + xoffset; vertices[index++] = posy - yoffset - ysize; vertices[index++] = posz; vertices[index++] = fchar.uvx; vertices[index++] = fchar.uvy + fchar.uvheight; vertices[index++] = color.x; vertices[index++] = color.y; vertices[index++] = color.z; vertices[index++] = color.w; // third vertex vertices[index++] = posx + xoffset + xsize; vertices[index++] = posy - yoffset - ysize; vertices[index++] = posz; vertices[index++] = fchar.uvx + fchar.uvwidth; vertices[index++] = fchar.uvy + fchar.uvheight; vertices[index++] = color.x; vertices[index++] = color.y; vertices[index++] = color.z; vertices[index++] = color.w; // first vertex vertices[index++] = posx + xoffset; vertices[index++] = posy - yoffset; vertices[index++] = posz; vertices[index++] = fchar.uvx; vertices[index++] = fchar.uvy; vertices[index++] = color.x; vertices[index++] = color.y; vertices[index++] = color.z; vertices[index++] = color.w; // third vertex vertices[index++] = posx + xoffset + xsize; vertices[index++] = posy - yoffset - ysize; vertices[index++] = posz; vertices[index++] = fchar.uvx + fchar.uvwidth; vertices[index++] = fchar.uvy + fchar.uvheight; vertices[index++] = color.x; vertices[index++] = color.y; vertices[index++] = color.z; vertices[index++] = color.w; // fourth vertex vertices[index++] = posx + xoffset + xsize; vertices[index++] = posy - yoffset; vertices[index++] = posz; vertices[index++] = fchar.uvx + fchar.uvwidth; vertices[index++] = fchar.uvy; vertices[index++] = color.x; vertices[index++] = color.y; vertices[index++] = color.z; vertices[index++] = color.w; posx = posx + fchar.xadvance * this.horizontalSizePpx; } return (vertices); } /** in percent depending on screen position (-1:1) */ public final void setPosition(float x, float y, float z) { this.set(x, y, z, this.getScaleX(), this.getScaleY(), this.getScaleZ(), this.getRotationX(), this.getRotationY(), this.getRotationZ(), this.getRotationCenterX(), this.getRotationCenterY(), this.getRotationCenterZ()); } public final void setX(float x) { this.setPosition(x, this.pos.y, this.pos.z); } public final void setY(float y) { this.setPosition(this.pos.x, y, this.pos.z); } public final void setZ(float z) { this.setPosition(this.pos.x, this.pos.y, z); } public final void setRotation(float rx, float ry, float rz) { this.set(this.getX(), this.getY(), this.getZ(), this.getScaleX(), this.getScaleY(), this.getScaleZ(), rx, ry, rz, this.getRotationCenterX(), this.getRotationCenterY(), this.getRotationCenterZ()); } public final Vector3f getRotation() { return (this.rot); } public final float getRotationX() { return (this.rot.x); } public final float getRotationY() { return (this.rot.y); } public final float getRotationZ() { return (this.rot.z); } public final void setRotationCenter(float rcx, float rcy, float rcz) { this.set(this.getX(), this.getY(), this.getZ(), this.getScaleX(), this.getScaleY(), this.getScaleZ(), this.getRotationX(), this.getRotationY(), this.getRotationZ(), rcx, rcy, rcz); } public final Vector3f getRotationCenter() { return (this.rotCenter); } public final float getRotationCenterX() { return (this.rotCenter.x); } public final float getRotationCenterY() { return (this.rotCenter.y); } public final float getRotationCenterZ() { return (this.rotCenter.z); } public void setScale(float sx, float sy, float sz) { this.set(this.getX(), this.getY(), this.getZ(), sx, sy, sz, this.getRotationX(), this.getRotationY(), this.getRotationZ(), this.getRotationCenterX(), this.getRotationCenterY(), this.getRotationCenterZ()); } public final void set(float x, float y, float z, float sx, float sy, float sz, float rx, float ry, float rz, float rcx, float rcy, float rcz) { this.pos.set(x, y, z); this.scale.set(sx, sy, sz); this.rot.set(rx, ry, rz); this.rotCenter.set(rcx, rcy, rcz); this.transfMatrix.setIdentity(); this.transfMatrix.translate(this.rotCenter); this.transfMatrix.rotateX(this.rot.x); this.transfMatrix.rotateY(this.rot.y); this.transfMatrix.rotateZ(this.rot.z); this.transfMatrix.translate(this.rotCenter.negate(new Vector3f())); this.transfMatrix.translate(this.pos); this.transfMatrix.scale(this.scale.x, this.scale.y, this.scale.z); } /** render this font model */ public void render() { if (this.hasState(FontModel.STATE_TEXT_UP_TO_DATE) == false) { this.updateText(); } if (this.vertexCount == 0 || this.font == null) { return; } GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); this.vao.bind(); this.font.getTexture().bind(GL13.GL_TEXTURE0, GL11.GL_TEXTURE_2D); this.vao.draw(GL11.GL_TRIANGLES, 0, this.vertexCount); } public boolean hasState(int state) { return ((this.state & state) == state); } private void setState(int state) { this.state = this.state | state; } private void unsetState(int state) { this.state = this.state & ~(state); } public Matrix4f getTransformationMatrix() { return (this.transfMatrix); } public String getText() { return (this.text); } @Override public String toString() { return ("FontModel : " + this.getText()); } public Font getFont() { return (this.font); } /** return text height in gl coordinate system */ public float getTextWidth() { return (this.textWidth * this.horizontalSizePpx); } /** return text height in gl coordinate system */ public float getTextHeight() { return (this.textHeight); } public Vector3f getScale() { return (this.scale); } public float getScaleX() { return (this.scale.x); } public float getScaleY() { return (this.scale.y); } public float getScaleZ() { return (this.scale.z); } public ArrayList<FontChar> getFontChar() { return (this.textChars); } /** request for an update of this font model */ public void requestUpdate() { this.unsetState(FontModel.STATE_TEXT_UP_TO_DATE); } public Vector4f getFontColor() { return (this.color); } public Vector3f getPosition() { return (this.pos); } public float getX() { return (this.pos.x); } public float getY() { return (this.pos.y); } public float getZ() { return (this.pos.z); } public float getBorderWidth() { return (this.borderWidth); } public void setBorderWidth(float value) { this.borderWidth = value; } public float getBorderEdge() { return (this.borderEdge); } public void setBorderEdge(float value) { this.borderEdge = value; } public Vector2f getOutlineOffset() { return (this.outlineOffset); } public void setOutlineOffset(float x, float y) { this.outlineOffset.set(x, y); } public Vector3f getOutlineColor() { return (this.outlineColor); } public void setOutlineColor(float r, float g, float b) { this.outlineColor.set(r, g, b); } /** * reset the outline and border parameters to default, to remove any effects */ public void clearEffects() { this.setBorderWidth(0.0f); this.setOutlineOffset(0, 0); this.setOutlineColor(0, 0, 0); } public final float getAspect() { return (this.aspect); } public final void setAspect(float aspect) { this.aspect = aspect; this.updateSizePpx(); this.unsetState(STATE_TEXT_UP_TO_DATE); } }