Click here to Skip to main content
15,898,222 members
Articles / Programming Languages / C#

Space and Matrix Transformations - Building a 3D Engine

Rate me:
Please Sign up or sign in to vote.
4.84/5 (13 votes)
4 Sep 2009CPOL8 min read 95K   6.1K   101  
This Article Describes How to Navigate 3D Space
#region Header
// --------------------------------------------------------------------------
//    Icarus Scene Engine
//    Copyright (C) 2005-2007  Euan D. MacInnes. All Rights Reserved.

//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.

//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.

//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
// --------------------------------------------------------------------------
//
//     UNIT               : FTclass.cs
//     SUMMARY            : Tao.FreeType OpenGL wrapper for uploading FreeType fonts to
//                        : OpenGL
//                        : 
//                        : 
//
//     PRINCIPLE AUTHOR   : Euan D. MacInnes
//     
#endregion Header
#region Revisions
// --------------------------------------------------------------------------
//     REVISIONS/NOTES
//        dd-mm-yyyy    By          Revision Summary
//
// --------------------------------------------------------------------------
#endregion Revisions


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using Tao.FreeType;
using Tao.OpenGl;
using System.Runtime.InteropServices;
using System.IO;

namespace ISE
{
    // for reporting errors
    public delegate void OnWriteEventType(string EventDetails);

    /// <summary>
    /// Glyph offset information for advanced rendering and/or conversions.
    /// </summary>
    public struct FTGlyphOffset
    {
        /// <summary>
        /// Width of the Glyph, in pixels.
        /// </summary>
        public int width;
        /// <summary>
        /// height of the Glyph, in pixels. Represents the number of scanlines
        /// </summary>
        public int height;
        /// <summary>
        /// For Bitmap-generated fonts, this is the top-bearing expressed in integer pixels.
        /// This is the distance from the baseline to the topmost Glyph scanline, upwards Y being positive.
        /// </summary>
        public int top;
        /// <summary>
        /// For Bitmap-generated fonts, this is the left-bearing expressed in integer pixels
        /// </summary>
        public int left;
        /// <summary>
        /// This is the transformed advance width for the glyph.
        /// </summary>
        public FT_Vector advance;
        /// <summary>
        /// The difference between hinted and unhinted left side bearing while autohinting is active. 0 otherwise.
        /// </summary>
        public long lsb_delta;
        /// <summary>
        /// The difference between hinted and unhinted right side bearing while autohinting is active. 0 otherwise.
        /// </summary>
        public long rsb_delta;
        /// <summary>
        /// The advance width of the unhinted glyph. Its value is expressed in 16.16 fractional pixels, unless FT_LOAD_LINEAR_DESIGN is set when loading the glyph. This field can be important to perform correct WYSIWYG layout. Only relevant for outline glyphs.
        /// </summary>
        public long linearHoriAdvance;
        /// <summary>
        /// The advance height of the unhinted glyph. Its value is expressed in 16.16 fractional pixels, unless FT_LOAD_LINEAR_DESIGN is set when loading the glyph. This field can be important to perform correct WYSIWYG layout. Only relevant for outline glyphs.
        /// </summary>
        public long linearVertAdvance;
    }

    /// <summary>
    /// For internal use, to represent the type of conversion to apply to the font
    /// </summary>
    public enum FTFontType
    {
        /// <summary>
        /// Font has not been initialised yet
        /// </summary>
        FT_NotInitialised,
        /// <summary>
        /// Font was converted to a series of Textures
        /// </summary>
        FT_Texture,
        /// <summary>
        /// Font was converted to a big texture map, representing a collection of glyphs
        /// </summary>
        FT_TextureMap,
        /// <summary>
        /// Font was converted to outlines and stored as display lists
        /// </summary>
        FT_Outline,
        /// <summary>
        /// Font was convered to Outliens and stored as Vertex Buffer Objects
        /// </summary>
        FT_OutlineVBO
    }

    /// <summary>
    /// Alignment of output text
    /// </summary> 
    public enum FTFontAlign
    {
        /// <summary>
        /// Left-align the text when it is drawn
        /// </summary>
        FT_ALIGN_LEFT,
        /// <summary>
        /// Center-align the text when it is drawn
        /// </summary>
        FT_ALIGN_CENTERED,
        /// <summary>
        /// Right-align the text when it is drawn
        /// </summary>
        FT_ALIGN_RIGHT
    }

    public static class FreeType
    {
        /// <summary>
        /// This event reports on the status of FreeType.
        /// This is useful to assign to this event to record down
        /// FreeType output to a debug log file, for example.
        /// </summary>
        public static OnWriteEventType OnWriteEvent;

        // Global FreeType library pointer
        public static System.IntPtr libptr = IntPtr.Zero;
    }

    /// <summary>
    /// Font class wraper for displaying FreeType fonts in OpenGL.
    /// </summary>
    public class FTFont
    {


        //Public members        
        private int list_base;
        private int font_size = 48;

        // Whether the font was loaded Ok or not
        private bool fontloaded = false;
        private int[] textures;
        private int[] extent_x;
        private FTGlyphOffset[] offsets;

        private System.IntPtr faceptr;
        private FT_FaceRec face;

        // debug variable used to list the state of all characters rendered
        private string sChars = "";

        private static void Report(string ErrorText)
        {
            if (FreeType.OnWriteEvent != null)
                FreeType.OnWriteEvent(ErrorText);
            else
                Console.WriteLine(ErrorText);
        }

        /// <summary>
        /// Initialise the FreeType library
        /// </summary>
        /// <returns></returns>
        public static int ftInit()
        {
            // We begin by creating a library pointer            
            if (FreeType.libptr == IntPtr.Zero)
            {
                int ret = FT.FT_Init_FreeType(out FreeType.libptr);

                if (ret != 0)
                {
                    Report("Failed to start FreeType");
                }
                else
                {
                    Report("FreeType Loaded.");
                    Report("FreeType Version " + ftVersionString());
                }

                return ret;
            }

            return 0;
        }

        /// <summary>
        /// Font alignment public parameter
        /// </summary>		
        public FTFontAlign FT_ALIGN = FTFontAlign.FT_ALIGN_LEFT;

        /// <summary>
        /// Initialise the Font. Will Initialise the freetype library if not already done so
        /// </summary>
        /// <param name="resourcefilename">Path to the external font file</param>
        /// <param name="Success">Returns 0 if successful</param>
        public FTFont(string resourcefilename, out int Success)
        {
            Report("Creating Font " + resourcefilename);
            Success = ftInit();

            if (FreeType.libptr == IntPtr.Zero) { Report("Couldn't start FreeType"); Success = -1; return; }

            string fontfile = resourcefilename;

            //Once we have the library we create and load the font face                       
            int retb = FT.FT_New_Face(FreeType.libptr, fontfile, 0, out faceptr);
            if (retb != 0)
            {
                if (!File.Exists(fontfile))
                    Report(fontfile + " not found.");
                else
                    Report(fontfile + " found.");

                Report("Failed to load font " + fontfile + " (error code " + retb.ToString() + ")");
                fontloaded = true;
                Success = retb;
                return;
            }
            fontloaded = true;

            Success = 0;
        }

        /// <summary>
        /// Return the version information for FreeType.
        /// </summary>
        /// <param name="Major">Major Version</param>
        /// <param name="Minor">Minor Version</param>
        /// <param name="Patch">Patch Number</param>
        public static void ftVersion(ref int Major, ref int Minor, ref int Patch)
        {
            ftInit();
            FT.FT_Library_Version(FreeType.libptr, ref Major, ref Minor, ref Patch);
        }

        /// <summary>
        /// Return the entire version information for FreeType as a String.
        /// </summary>
        /// <returns></returns>
        public static string ftVersionString()
        {
            int major = 0;
            int minor = 0;
            int patch = 0;
            ftVersion(ref major, ref minor, ref patch);
            return major.ToString() + "." + minor.ToString() + "." + patch.ToString();
        }

        /// <summary>
        /// Render the font to a series of OpenGL textures (one per letter)
        /// </summary>
        /// <param name="fontsize">size of the font</param>
        /// <param name="DPI">dots-per-inch setting</param>
        public void ftRenderToTexture(int fontsize, uint DPI)
        {
            if (!fontloaded) return;
            font_size = fontsize;

            if (faceptr == IntPtr.Zero)
            {
                Report("ERROR: No Face Pointer. Font was not created properly");
                return;
            }

            face = (FT_FaceRec)Marshal.PtrToStructure(faceptr, typeof(FT_FaceRec));
            Report("Num Faces:" + face.num_faces.ToString());
            Report("Num Glyphs:" + face.num_glyphs.ToString());
            Report("Num Char Maps:" + face.num_charmaps.ToString());
            Report("Font Family:" + face.family_name);
            Report("Style Name:" + face.style_name);
            Report("Generic:" + face.generic);
            Report("Bbox:" + face.bbox);
            Report("Glyph:" + face.glyph);
            //   IConsole.Write("Num Glyphs:", );

            //Freetype measures the font size in 1/64th of pixels for accuracy 
            //so we need to request characters in size*64
            FT.FT_Set_Char_Size(faceptr, font_size << 6, font_size << 6, DPI, DPI);

            //Provide a reasonably accurate estimate for expected pixel sizes
            //when we later on create the bitmaps for the font
            FT.FT_Set_Pixel_Sizes(faceptr, (uint)font_size, (uint)font_size);


            // Once we have the face loaded and sized we generate opengl textures 
            // from the glyphs  for each printable character
            Report("Compiling Font Characters 0..127");
            textures = new int[128];
            extent_x = new int[128];
            offsets = new FTGlyphOffset[128];
            list_base = Gl.glGenLists(128);
            Gl.glGenTextures(128, textures);
            for (int c = 0; c < 128; c++)
            {
                CompileCharacterToTexture(face, c);

                if (c < 127)
                    sChars += ",";
            }
            //Console.WriteLine("Font Compiled:" + sChars);            
        }

        internal int next_po2(int a)
        {
            int rval = 1;
            while (rval < a) rval <<= 1;
            return rval;
        }


        private void CompileCharacterToTexture(FT_FaceRec face, int c)
        {
            FTGlyphOffset offset = new FTGlyphOffset();

            //We first convert the number index to a character index
            uint index = FT.FT_Get_Char_Index(faceptr, (uint)c);

            string sError = "";
            if (index == 0) sError = "No Glyph";

            //Here we load the actual glyph for the character
            int ret = FT.FT_Load_Glyph(faceptr, index, FT.FT_LOAD_DEFAULT);
            if (ret != 0)
            {
                Report("Load_Glyph failed for character " + c.ToString());
            }

            FT_GlyphSlotRec glyphrec = (FT_GlyphSlotRec)Marshal.PtrToStructure(face.glyph, typeof(FT_GlyphSlotRec));

            ret = FT.FT_Render_Glyph(ref glyphrec, FT_Render_Mode.FT_RENDER_MODE_NORMAL);

            if (ret != 0)
            {
                Report("Render failed for character " + c.ToString());
            }

            int size = (glyphrec.bitmap.width * glyphrec.bitmap.rows);
            if (size <= 0)
            {
                //Console.Write("Blank Character: " + c.ToString());
                //space is a special `blank` character
                extent_x[c] = 0;
                if (c == 32)
                {
                    Gl.glNewList((list_base + c), Gl.GL_COMPILE);
                    Gl.glTranslatef(font_size >> 1, 0, 0);
                    extent_x[c] = font_size >> 1;
                    Gl.glEndList();
                    offset.left = 0;
                    offset.top = 0;
                    offset.height = 0;
                    offset.width = extent_x[c];
                    offsets[c] = offset;
                }
                return;

            }

            byte[] bmp = new byte[size];
            Marshal.Copy(glyphrec.bitmap.buffer, bmp, 0, bmp.Length);

            //Next we expand the bitmap into an opengl texture 	    	
            int width = next_po2(glyphrec.bitmap.width);
            int height = next_po2(glyphrec.bitmap.rows);

            byte[] expanded = new byte[2 * width * height];
            for (int j = 0; j < height; j++)
            {
                for (int i = 0; i < width; i++)
                {
                    //Luminance
                    expanded[2 * (i + j * width)] = (byte)255;
                    //expanded[4 * (i + j * width) + 1] = (byte)255;
                    //expanded[4 * (i + j * width) + 2] = (byte)255;

                    // Alpha
                    expanded[2 * (i + j * width) + 1] =
                        (i >= glyphrec.bitmap.width || j >= glyphrec.bitmap.rows) ?
                        (byte)0 : (byte)(bmp[i + glyphrec.bitmap.width * j]);
                }
            }

            //Set up some texture parameters for opengl
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, textures[c]);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP_TO_EDGE);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP_TO_EDGE);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);

            //Create the texture
            Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, width, height,
                0, Gl.GL_LUMINANCE_ALPHA, Gl.GL_UNSIGNED_BYTE, expanded);
            expanded = null;
            bmp = null;

            //Create a display list and bind a texture to it
            Gl.glNewList((list_base + c), Gl.GL_COMPILE);
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, textures[c]);

            //Account for freetype spacing rules
            Gl.glTranslatef(glyphrec.bitmap_left, 0, 0);
            Gl.glPushMatrix();
            Gl.glTranslatef(0, glyphrec.bitmap_top - glyphrec.bitmap.rows, 0);
            float x = (float)glyphrec.bitmap.width / (float)width;
            float y = (float)glyphrec.bitmap.rows / (float)height;

            offset.left = glyphrec.bitmap_left;
            offset.top = glyphrec.bitmap_top;
            offset.height = glyphrec.bitmap.rows;
            offset.width = glyphrec.bitmap.width;
            offset.advance = glyphrec.advance;
            offset.lsb_delta = glyphrec.lsb_delta;
            offset.rsb_delta = glyphrec.rsb_delta;
            offset.linearHoriAdvance = glyphrec.linearHoriAdvance;
            offset.linearVertAdvance = glyphrec.linearVertAdvance;
            offsets[c] = offset;

            //Draw the quad
            Gl.glBegin(Gl.GL_QUADS);
            Gl.glTexCoord2d(0, 0); Gl.glVertex2f(0, glyphrec.bitmap.rows);
            Gl.glTexCoord2d(0, y); Gl.glVertex2f(0, 0);
            Gl.glTexCoord2d(x, y); Gl.glVertex2f(glyphrec.bitmap.width, 0);
            Gl.glTexCoord2d(x, 0); Gl.glVertex2f(glyphrec.bitmap.width, glyphrec.bitmap.rows);
            Gl.glEnd();
            Gl.glPopMatrix();

            //Advance for the next character			
            Gl.glTranslatef(glyphrec.bitmap.width, 0, 0);
            extent_x[c] = glyphrec.bitmap_left + glyphrec.bitmap.width;
            Gl.glEndList();
            sChars += "f:" + c.ToString() + "[w:" + glyphrec.bitmap.width.ToString() + "][h:" + glyphrec.bitmap.rows.ToString() + "]" + sError;
        }

        /// <summary>
        /// Dispose of the font
        /// </summary>
        public void Dispose()
        {
            ftClearFont();
            // Dispose of these as we don't need
            if (faceptr != IntPtr.Zero)
            {
                FT.FT_Done_Face(faceptr);
                faceptr = IntPtr.Zero;
            }

        }

        /// <summary>
        /// Dispose of the FreeType library
        /// </summary>
        public static void DisposeFreeType()
        {
            FT.FT_Done_FreeType(FreeType.libptr);
        }

        /// <summary>
        /// Clear all OpenGL-related structures.
        /// </summary>
        public void ftClearFont()
        {

            if (list_base > 0)
                Gl.glDeleteLists(list_base, 128);

            if (textures != null)
                Gl.glDeleteTextures(128, textures);

            textures = null;
            extent_x = null;
        }

        /// <summary>
        /// Return the horizontal extent (width),in pixels, of a given font string
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public virtual float ftExtent(ref string text)
        {
            int ret = 0;
            for (int c = 0; c < text.Length; c++)
                ret += extent_x[text[c]];
            return ret;
        }

        /// <summary>
        /// Return the Glyph offsets for the first character in "text"
        /// </summary>
        public FTGlyphOffset ftGetGlyphOffset(Char glyphchar)
        {
            return offsets[glyphchar];
        }

        /// <summary>
        /// Initialise the OpenGL state necessary fo rendering the font properly
        /// </summary>
        public void ftBeginFont()
        {
            int font = list_base;

            //Prepare openGL for rendering the font characters      
            Gl.glPushAttrib(Gl.GL_LIST_BIT | Gl.GL_CURRENT_BIT | Gl.GL_ENABLE_BIT | Gl.GL_TRANSFORM_BIT);
            Gl.glDisable(Gl.GL_LIGHTING);
            Gl.glEnable(Gl.GL_TEXTURE_2D);
            Gl.glDisable(Gl.GL_DEPTH_TEST);
            Gl.glEnable(Gl.GL_BLEND);
            Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
            Gl.glListBase(font);
        }


        #region ftWrite(string text)
        /// <summary>
        ///     Custom GL "write" routine.
        /// </summary>
        /// <param name="text">
        ///     The text to print.
        /// </param>
        public void ftWrite(string text)
        {
            // Scale to fit on Projection rendering screen with default coordinates                        
            Gl.glPushMatrix();

            switch (FT_ALIGN)
            {
                case FTFontAlign.FT_ALIGN_CENTERED:
                    Gl.glTranslatef(-ftExtent(ref text) / 2, 0, 0);
                    break;

                case FTFontAlign.FT_ALIGN_RIGHT:
                    Gl.glTranslatef(-ftExtent(ref text), 0, 0);
                    break;

                //By default it is left-aligned, so there's no need to bother translating by 0.
            }


            //Render
            byte[] textbytes = new byte[text.Length];
            for (int i = 0; i < text.Length; i++)
                textbytes[i] = (byte)text[i];
            Gl.glCallLists(text.Length, Gl.GL_UNSIGNED_BYTE, textbytes);
            textbytes = null;
            Gl.glPopMatrix();
        }
        #endregion

        /// <summary>
        /// Restore the OpenGL state to what it was prior
        /// to starting to draw the font
        /// </summary>
        public void ftEndFont()
        {
            //Restore openGL state
            //Gl.glPopMatrix();
            Gl.glPopAttrib();
        }
    }
}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer Lea+Elliott, Inc.
United States United States
I am a licensed Electrical Engineer at Lea+Elliott, Inc. We specialize in the planning, procurement and implementation of transportation systems, with special emphasis on automated and emerging technologies.

Comments and Discussions