using System; using System.Collections.Generic; using System.IO; using OpenGL; namespace OpenGLLabs { public class FontVAO : IDisposable { private ShaderProgram program; private VBO vertices; private VBO uvs; private VBO triangles; public Vector2 Position { get; set; } public FontVAO(ShaderProgram program, VBO vertices, VBO uvs, VBO triangles) { this.program = program; this.vertices = vertices; this.uvs = uvs; this.triangles = triangles; } public void Draw() { if (vertices == null) return; program.Use(); program["model_matrix"].SetValue(Matrix4.CreateTranslation(new Vector3(Position.X, Position.Y, 0))); Gl.BindBufferToShaderAttribute(vertices, program, "vertexPosition"); Gl.BindBufferToShaderAttribute(uvs, program, "vertexUV"); Gl.BindBuffer(triangles); Gl.DrawElements(BeginMode.Triangles, triangles.Count, DrawElementsType.UnsignedInt, IntPtr.Zero); } public void Dispose() { vertices.Dispose(); uvs.Dispose(); triangles.Dispose(); vertices = null; } } /// /// The BMFont class can be used to load both the texture and data files associated with /// the free BMFont tool (http://www.angelcode.com/products/bmfont/) /// This tool allows /// public class BMFont { /// /// Stores the ID, height, width and UV information for a single bitmap character /// as exported by the BMFont tool. /// private struct Character { public char id; public Vector2 texturePosition; public Vector2 size; public Vector2 bearing; public int advance; public Character(char id, Vector2 texturePosition, Vector2 size, Vector2 bearing, int advance) { this.id = id; this.texturePosition = texturePosition; this.size = size; this.bearing = bearing; this.advance = advance; } } /// /// Text justification to be applied when creating the VAO representing some text. /// public enum Justification { Left, Center, Right } /// /// The font texture associated with this bitmap font. /// public Texture FontTexture { get; private set; } private Dictionary characters = new Dictionary(); /// /// The height (in pixels) of this bitmap font. /// public int Height { get; private set; } /// /// Loads both a font descriptor table and the associated texture as exported by BMFont. /// /// The path to the font descriptor table. /// The path to the font texture. public BMFont(string descriptorPath, string texturePath) { this.FontTexture = new Texture(texturePath); using (StreamReader stream = new StreamReader(descriptorPath)) { while (!stream.EndOfStream) { string line = stream.ReadLine(); if (line.StartsWith("char")) { // chars lets the program know how many characters are in this file, ignore it if (line.StartsWith("chars")) continue; // split up the different entries on this line to be parsed string[] split = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); int id = 0, advance = 0; float x = 0, y = 0, w = 0, h = 0, xoffset = 0, yoffset = 0; // parse the contents of the line, looking for key words for (int i = 0; i < split.Length; i++) { if (!split[i].Contains("=")) continue; string code = split[i].Substring(0, split[i].IndexOf('=')); int value = int.Parse(split[i].Substring(split[i].IndexOf('=') + 1)); if (code == "id") id = value; else if (code == "x") x = (float)value / FontTexture.Size.Width; else if (code == "y") y = 1 - (float)value / FontTexture.Size.Height; else if (code == "width") w = (float)value; else if (code == "height") { h = (float)value; this.Height = Math.Max(this.Height, value); } else if (code == "xoffset") xoffset = (float)value; else if (code == "yoffset") yoffset = (float)value; else if (code == "xadvance") advance = value; } // store this character into our dictionary (if it doesn't already exist) Character c = new Character((char)id, new Vector2(x, y), new Vector2(w, h), new Vector2(xoffset, yoffset), advance); if (!characters.ContainsKey(c.id)) characters.Add(c.id, c); } } } } /// /// Gets the width (in pixels) of a string of text using the current loaded font. /// /// The string of text to measure the width of. /// The width (in pixels) of the provided text. public int GetWidth(string text) { int width = 0; for (int i = 0; i < text.Length; i++) width += (int)characters[characters.ContainsKey(text[i]) ? text[i] : ' '].advance; return width; } public FontVAO CreateString(ShaderProgram program, string text, Justification justification = Justification.Left) { Vector3[] vertices = new Vector3[text.Length * 4]; Vector2[] uvs = new Vector2[text.Length * 4]; uint[] indices = new uint[text.Length * 6]; int xpos = 0, width = 0; // calculate the initial x position depending on the justification if (justification != Justification.Left) { for (int i = 0; i < text.Length; i++) width += (int)characters[characters.ContainsKey(text[i]) ? text[i] : ' '].advance; if (justification == Justification.Right) xpos = -width; else xpos = -width / 2; } for (uint i = 0; i < text.Length; i++) { // grab the character, replacing with ' ' if the character isn't loaded Character ch = characters[characters.ContainsKey(text[(int)i]) ? text[(int)i] : ' ']; vertices[i * 4 + 0] = new Vector3(xpos + ch.bearing.X, Height - ch.bearing.Y, 0); vertices[i * 4 + 1] = new Vector3(xpos + ch.bearing.X, Height - (ch.bearing.Y + ch.size.Y), 0); vertices[i * 4 + 2] = new Vector3(xpos + ch.bearing.X + ch.size.X, Height - ch.bearing.Y, 0); vertices[i * 4 + 3] = new Vector3(xpos + ch.bearing.X + ch.size.X, Height - (ch.bearing.Y + ch.size.Y), 0); xpos += ch.advance; Vector2 bottomRight = ch.texturePosition + new Vector2(ch.size.X / FontTexture.Size.Width, -ch.size.Y / FontTexture.Size.Height); uvs[i * 4 + 0] = new Vector2(ch.texturePosition.X, ch.texturePosition.Y); uvs[i * 4 + 1] = new Vector2(ch.texturePosition.X, bottomRight.Y); uvs[i * 4 + 2] = new Vector2(bottomRight.X, ch.texturePosition.Y); uvs[i * 4 + 3] = new Vector2(bottomRight.X, bottomRight.Y); indices[i * 6 + 0] = i * 4 + 2; indices[i * 6 + 1] = i * 4 + 0; indices[i * 6 + 2] = i * 4 + 1; indices[i * 6 + 3] = i * 4 + 3; indices[i * 6 + 4] = i * 4 + 2; indices[i * 6 + 5] = i * 4 + 1; } // Create the vertex buffer objects and then create the array object return new FontVAO(program, new VBO(vertices), new VBO(uvs), new VBO(indices, BufferTarget.ElementArrayBuffer)); } public static string FontVertexSource = @" #version 130 uniform mat4 model_matrix; uniform mat4 ortho_matrix; in vec3 vertexPosition; in vec2 vertexUV; out vec2 uv; void main(void) { uv = vertexUV; gl_Position = ortho_matrix * model_matrix * vec4(vertexPosition, 1); }"; public static string FontFragmentSource = @" #version 130 uniform sampler2D texture; uniform vec3 color; in vec2 uv; out vec4 fragment; void main(void) { vec4 texel = texture2D(texture, uv); fragment = vec4(texel.rgb * color, texel.a); } "; } }