package org.goko.tools.viewer.jogl.utils.render.text.v2; import java.nio.FloatBuffer; import java.nio.IntBuffer; import javax.media.opengl.GL; import javax.media.opengl.GL3; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; import javax.vecmath.Vector4f; import org.apache.commons.lang3.StringUtils; import org.goko.core.common.exception.GkException; import org.goko.core.common.measure.quantity.Length; import org.goko.tools.viewer.jogl.service.JoglUtils; import org.goko.tools.viewer.jogl.shaders.EnumGokoShaderProgram; import org.goko.tools.viewer.jogl.shaders.ShaderLoader; import org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer; import com.jogamp.common.nio.Buffers; import com.jogamp.opengl.util.texture.Texture; public class TextRenderer extends AbstractVboJoglRenderer { /** Channel for each char layout */ private static final int CHAR_CHANNEL_LAYOUT = 5; public static final int LEFT = 0; public static final int CENTER = 1 << 1; public static final int RIGHT = 1 << 2; public static final int TOP = 1 << 3; public static final int MIDDLE = 1 << 4; public static final int BOTTOM = 0; private static final int HORIZONTAL_MASK = LEFT | CENTER | RIGHT; private static final int VERTICAL_MASK = TOP | MIDDLE | BOTTOM; private Point3d position; private String text; private Vector3d widthVector; private Vector3d heightVector; private double size; private Vector4f color; private Texture texture; private EnumBitmapFont enumBitmapFont; private BitmapFontFile bff; private int alignement; private int textureSize; /** The image channel of each char buffer object*/ private Integer charChannelBufferObject; /** Int buffer for char channel*/ private IntBuffer charChannelBuffer; private Length verticalPadding; private Length horizontalPadding; public TextRenderer(String text, double size, Point3d position) { this(text, size, position, new Vector3d(1,0,0), new Vector3d(0,1,0)); } public TextRenderer(String text, double size, Point3d position, int alignement) { this(text, size, position, new Vector3d(1,0,0), new Vector3d(0,1,0), alignement); } public TextRenderer(String text, double size, Point3d position, Vector3d widthVector, Vector3d heightVector) { this(text, size, position, widthVector, heightVector, CENTER | MIDDLE); } //le text renderer a du mal sur les plans autres que XY public TextRenderer(String text, double size, Point3d position, Vector3d widthVector, Vector3d heightVector, int alignement) { super(GL.GL_TRIANGLES, VERTICES | COLORS | UVS); this.text = text; this.widthVector = new Vector3d(widthVector); this.heightVector = new Vector3d(heightVector); this.size = size; this.position = new Point3d(position); this.enumBitmapFont = EnumBitmapFont.CONSOLAS; this.color = new Vector4f(1,1,1,1); this.alignement = alignement; this.verticalPadding = Length.ZERO; this.horizontalPadding = Length.ZERO; } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#buildGeometry() */ @Override protected void buildGeometry() throws GkException { setVerticesCount(StringUtils.length(text)*6); charChannelBuffer = IntBuffer.allocate(3*6*getText().length()); generateVertices(); } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#performInitialize(javax.media.opengl.GL3) */ @Override protected void performInitialize(GL3 gl) throws GkException { IntBuffer intBuffer = IntBuffer.allocate(1); gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, intBuffer); textureSize = Math.min(2048, intBuffer.get()); this.bff = BitmapFontFileManager.getBitmapFontFile(enumBitmapFont, textureSize); texture = BitmapFontFileManager.getTextureFont(gl, enumBitmapFont, textureSize); texture.setTexParameteri(gl,GL.GL_TEXTURE_MIN_FILTER,GL.GL_LINEAR); texture.setTexParameteri(gl,GL.GL_TEXTURE_MAG_FILTER,GL.GL_LINEAR); setShaderProgram(loadShaderProgram(gl)); super.performInitialize(gl); } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#initializeAdditionalBufferObjects(javax.media.opengl.GL3) */ @Override protected void initializeAdditionalBufferObjects(GL3 gl) throws GkException { super.initializeAdditionalBufferObjects(gl); gl.glActiveTexture(GL3.GL_TEXTURE0); texture.enable(gl); texture.bind(gl); // Initialize the channelbuffer object if(this.charChannelBufferObject == null){ int[] vbo = new int[1]; gl.glGenBuffers(1, vbo, 0); this.charChannelBufferObject = vbo[0]; } // Make sure we take everything charChannelBuffer.rewind(); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, charChannelBufferObject); gl.glBufferData(GL.GL_ARRAY_BUFFER, getText().length()*6*3*Buffers.SIZEOF_INT, charChannelBuffer, GL.GL_STATIC_DRAW); gl.glEnableVertexAttribArray(CHAR_CHANNEL_LAYOUT); } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#enableAdditionalVertexAttribArray(javax.media.opengl.GL3) */ @Override protected void enableAdditionalVertexAttribArray(GL3 gl) throws GkException { texture.enable(gl); gl.glEnableVertexAttribArray(CHAR_CHANNEL_LAYOUT); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, charChannelBufferObject); gl.glVertexAttribPointer(CHAR_CHANNEL_LAYOUT, 3, GL3.GL_INT, false, 0, 0); } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#disableAdditionalVertexAttribArray(javax.media.opengl.GL3) */ @Override protected void disableAdditionalVertexAttribArray(GL3 gl) throws GkException { super.disableAdditionalVertexAttribArray(gl); texture.disable(gl); gl.glDisableVertexAttribArray(CHAR_CHANNEL_LAYOUT); } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#loadShaderProgram(javax.media.opengl.GL3) */ @Override protected int loadShaderProgram(GL3 gl) throws GkException { return ShaderLoader.loadShader(gl, EnumGokoShaderProgram.TEXT_SHADER); } protected void generateVertices() throws GkException { FloatBuffer verticesBuffer = FloatBuffer.allocate(4*getVerticesCount()); FloatBuffer uvsBuffer = FloatBuffer.allocate(2*getVerticesCount()); FloatBuffer colorsBuffer = FloatBuffer.allocate(4*getVerticesCount()); Point3d lowerLeft = computeTopLeftCorner(); Point3d p1 = new Point3d(lowerLeft); int length = StringUtils.length(text); for (int i = 0; i < length; i++) { generateBuffers(text.charAt(i), p1, verticesBuffer, colorsBuffer, uvsBuffer); } setVerticesBuffer(verticesBuffer); setColorsBuffer(colorsBuffer); setUvsBuffer(uvsBuffer); } private Point3d computeTopLeftCorner() { Point3d topLeft = new Point3d(position); int length = StringUtils.length(text); double textWidth = 0; double ratio = size / bff.getLineHeight(); double minYOffset = Double.MAX_VALUE; // Compute width by summing the width of each char for (int i = 0; i < length; i++) { char letter = text.charAt(i); CharBlock info = bff.getCharacterInfo(letter); textWidth += info.getWidth(); minYOffset = Math.min(minYOffset, info.getYoffset()); } textWidth = ratio * textWidth; Vector3d wVector = new Vector3d(widthVector); Vector3d hVector = new Vector3d(heightVector); Vector3d hPadding = new Vector3d(widthVector); hPadding.scale(horizontalPadding.doubleValue(JoglUtils.JOGL_UNIT)); Vector3d vPadding = new Vector3d(heightVector); vPadding.scale(verticalPadding.doubleValue(JoglUtils.JOGL_UNIT)); switch(this.alignement & HORIZONTAL_MASK ){ case RIGHT: wVector.scale((float) textWidth); wVector.add(hPadding); topLeft.sub(wVector); break; case CENTER: wVector.scale(0.5 * textWidth); topLeft.sub(wVector); break; default: topLeft.add(hPadding); break; } switch(this.alignement & VERTICAL_MASK ){ case BOTTOM: hVector.scale(ratio * bff.getBase());//ratio*bff.getLineHeight());// * (1 - (lineHeight - base) / lineHeight)); hVector.add(vPadding); topLeft.add(hVector); break; case TOP: hVector.scale(ratio*minYOffset);//* ( (base - maxCharHeight) / lineHeight)); hVector.sub(vPadding); topLeft.add(hVector); break; case MIDDLE: hVector.scale(0.5f); topLeft.add(hVector); break; default: // Nothing here break; } return topLeft; } private void generateBuffers(char letter, Point3d position, FloatBuffer vertices, FloatBuffer colors, FloatBuffer uvs){ CharBlock charInfo = bff.getCharacterInfo(letter); float textureWidth = bff.getTextureWidth(); float textureHeight = bff.getTextureHeight(); float fontBase = bff.getBase(); float fontLineHeight= bff.getLineHeight(); double ratio = size / bff.getLineHeight(); float charTextureWidth = charInfo.getWidth(); float charTextureHeight = charInfo.getHeight(); float charYOffset = charInfo.getYoffset(); float charXOffset = charInfo.getXoffset(); Vector3d wVector = new Vector3d(widthVector); Vector3d hVector = new Vector3d(heightVector); // Let's calculate the vector from the top to the base line of the font Vector3d baselineOffset = new Vector3d(heightVector); baselineOffset.scale( charYOffset * ratio); wVector.scale(charTextureWidth * ratio); hVector.scale(charTextureHeight * ratio); // +y // | 1----2 1 = top left of image (0,0) 2 = top right (1,0) // | | | 3 = bottom left (0, 1) 4 = bottom right (1,1) // | | | // | 3----4 // +----------> +x // float u1 = (charInfo.getX())/textureWidth; float v1 = charInfo.getY()/textureHeight; float u2 = (charInfo.getX() + charTextureWidth)/textureWidth; float v2 = charInfo.getY()/textureHeight; float u3 = (charInfo.getX())/textureWidth; float v3 = (charInfo.getY() + charTextureHeight)/textureHeight; float u4 = (charInfo.getX() + charTextureWidth)/textureWidth; float v4 = (charInfo.getY() + charTextureHeight )/textureHeight; //adapter la taille des polygones générés Point3d p1 = new Point3d(position.x-baselineOffset.x ,position.y-baselineOffset.y ,position.z-baselineOffset.z); Point3d p2 = new Point3d(position.x-baselineOffset.x+wVector.x ,position.y-baselineOffset.y+wVector.y ,position.z-baselineOffset.z+wVector.z); Point3d p3 = new Point3d(position.x-baselineOffset.x-hVector.x ,position.y-baselineOffset.y-hVector.y ,position.z-baselineOffset.z-hVector.z); Point3d p4 = new Point3d(position.x-baselineOffset.x-hVector.x+wVector.x,position.y-baselineOffset.y-hVector.y+wVector.y ,position.z-baselineOffset.z-hVector.z+wVector.z); int[] channel = {0,0,0}; channel[charInfo.getChannel()] = 1; // One for each vertice charChannelBuffer.put(channel); charChannelBuffer.put(channel); charChannelBuffer.put(channel); charChannelBuffer.put(channel); charChannelBuffer.put(channel); charChannelBuffer.put(channel); vertices.put(new float[]{(float)p1.x, (float)p1.y, (float)p1.z, 1}); colors.put(new float[]{color.x,color.y,color.z,color.w}); uvs.put(new float[]{u1, v1}); vertices.put(new float[]{(float)p2.x, (float)p2.y, (float)p2.z, 1}); colors.put(new float[]{color.x,color.y,color.z,color.w}); uvs.put(new float[]{u2, v2}); vertices.put(new float[]{(float)p3.x, (float)p3.y, (float)p3.z, 1}); colors.put(new float[]{color.x,color.y,color.z,color.w}); uvs.put(new float[]{u3, v3}); vertices.put(new float[]{(float)p2.x, (float)p2.y, (float)p2.z, 1}); colors.put(new float[]{color.x,color.y,color.z,color.w}); uvs.put(new float[]{u2, v2}); vertices.put(new float[]{(float)p3.x, (float)p3.y, (float)p3.z, 1}); colors.put(new float[]{color.x,color.y,color.z,color.w}); uvs.put(new float[]{u3, v3}); vertices.put(new float[]{(float)p4.x, (float)p4.y, (float)p4.z, 1}); colors.put(new float[]{color.x,color.y,color.z,color.w}); uvs.put(new float[]{u4, v4}); // We do not use XAdvance on purpose position.add(wVector); } /** * @return the text */ public String getText() { return text; } /** * @param text the text to set */ public void setText(String text) { this.text = text; } /** * @return the widthVector */ public Vector3d getWidthVector() { return widthVector; } /** * @param widthVector the widthVector to set */ public void setWidthVector(Vector3d widthVector) { this.widthVector = widthVector; } /** * @return the heightVector */ public Vector3d getHeightVector() { return heightVector; } /** * @param heightVector the heightVector to set */ public void setHeightVector(Vector3d heightVector) { this.heightVector = heightVector; } /** * @return the size */ public double getSize() { return size; } /** * @param size the size to set */ public void setSize(double size) { this.size = size; } /** * @return the color */ public Vector4f getColor() { return color; } /** * @param color the color to set */ public void setColor(double r, double g, double b, double a) { this.color = new Vector4f((float)r,(float)g,(float)b,(float)a); } /** * @param color the color to set */ protected void setColor(Vector4f color) { this.color = color; } /** * @return the verticalPadding */ public Length getVerticalPadding() { return verticalPadding; } public TextRenderer setPadding(Length padding){ setVerticalPadding(padding); setHorizontalPadding(padding); return this; } /** * @param verticalPadding the verticalPadding to set */ public void setVerticalPadding(Length verticalPadding) { this.verticalPadding = verticalPadding; } /** * @return the horizontalPadding */ public Length getHorizontalPadding() { return horizontalPadding; } /** * @param horizontalPadding the horizontalPadding to set */ public void setHorizontalPadding(Length horizontalPadding) { this.horizontalPadding = horizontalPadding; } }