Tutorial 22 - Texto 2D
Fecha Lunes, 01 septiembre a las 11:10:12
Tema DirectX/Opengl


Este tutorial quizás debería haberlo hecho al principio cuando veíamos las 2D, pero bueno, nunca es tarde.

Vamos a dibujar texto 2D mediante el uso de una textura con el juego de caracteres ASCII del idioma en el que trabajemos.



Para obtener dicha textura lo que hice fué usar un procesador de texto e ir escribiendo los caracteres en orden ASCII creciente, esto es, mediante la combinación de teclas ALT-GR+[CODIGO-ASCII] se iban mostrando los caracteres. Usé una fuente monoespacio (por ejemplo Courier New), lo que quiere decir, es que TODOS los caracteres los representa con EL MISMO ANCHO. Lo cual es muy importante para poder mapear luego la textura.

Por comodidad, la textura TABLA-ASCII-160x713x24.bmp contiene 10 caracteres por fila. Esto nos permite comprobar más fácilmente cómo se realizan los cálculos al dividir por 10.

La idea es sencilla, calculamos cuántos píxeles de ancho y alto tiene cada carácter. Y el salto que habrá que realizar para encontrar el carácter no es más que obtener su código ASCII y dividir por 10 para saber en qué fila se encuentra y posteriormente el resto de la división nos dará su ubicación en la fila.

Debido a que los primeros caractes ASCII son de control (no imprimibles) el primer carácter representado es el espacio en blanco, y por ello se lo restamos a la hora de obtener el desplazamiento dentro de la textura.

Utilizaremos la técnica de color-key para poder dibujar el texto sobre otros elementos.

Se ha definido la clase Font2D para gestionar el uso del texto. Básicamente lo único que tiene es la función LoadTexture() para cargar la textura y fijar los parámetros que definen sus dimensiones y los elementos que la componen y la función de renderizado.


    ///////////////////////////////////////////////////////////////////////////////
    ///     LoadTexture: Inicializa las dimensiones del texto.
    ///
    ///     @param  char *fileName: Nombre del fichero de la textura.
    ///     @param  int width: Ancho de la textura.
    ///     @param  int height: Alto de la textura.
    ///     @param  int charsPerLine: Nº de caracteres por línea en el bitmap.
    ///     @param  int totalChars: Nº total de caracteres en el bitmap.
    ///     @param  ulColorRGBA colorKey: Color de la textura que será transparente.
    ///
    ///     @return  int:   
    ///                     - -1  : error.
    ///                     - otro: correcto.
    ///////////////////////////////////////////////////////////////////////////////
    int Font2D::LoadTexture( char *fileName, int width, int height, int charsPerLine,
                             int totalChars, ulColorRGBA colorKey )
    {
        IVideoDriver *pVideoDriver= IVideoDriver::GetVideoDriver();

        if ( pVideoDriver )
        {
            m_CharsPerLine  = charsPerLine;
            m_WidthChar     = (width  / (float)m_CharsPerLine );
            m_HeightChar    = (height / (float)(totalChars/m_CharsPerLine) );
            m_WidthBitmap   = width;
            m_HeightBitmap  = height;

            return pVideoDriver->LoadTexture( fileName, m_FontTexture, colorKey );
        }
        else
        {
            return -1;
        }
    }

    ///////////////////////////////////////////////////////////////////////////////
    ///     Render2DText: Muestra el texto en pantalla.
    ///
    ///     @param  int x: Coordenada x de pantalla donde se mostrará el texto.
    ///     @param  int y: Coordenada y de pantalla donde se mostrará el texto.
    ///     @param  char *text: Mensaje de texto a mostrar.
    ///     @param  unsigned int rgbColor: Color del texto a mostrar.
    ///
    ///     @return  nada
    ///////////////////////////////////////////////////////////////////////////////
    void Font2D::Render2DText( int x, int y, char *text, unsigned int rgbColor )
    {
        int len;
        float xOffset, yOffset;
        static VertexFont2D vertexArray[4];

        IVideoDriver *pVideoDriver= IVideoDriver::GetVideoDriver();

        if ( pVideoDriver )
        {
            if ( text )
            {
                len= strlen(text);

                pVideoDriver->EnableBlending( true );
                pVideoDriver->SetBlending( /*fuente*/eBlendSrcAlpha,
                                          /*destino*/eBlendOneMinusSrcAlpha );

                for ( int i=0; ifloat)( ((unsigned char)text[i]-'' '')%m_CharsPerLine );
                    yOffset= (float)( ((unsigned char)text[i]-'' '')/m_CharsPerLine );

                    // Definimos un cuadrado con dos triángulos.
                    //
                    // v0   v2
                    //  *  /*
                    //  | / |
                    //  |/  |
                    //  *   *
                    // v1   v3
                    //

                    if ( pVideoDriver->GetAxisSystem() == eLeftHanded )
                    {
                        // Con sistema de ejes de mano izquierda, habría que recorrer 
                        // los vértices en el orden 1,0,2,3 en lugar de 0,1,2,3.
                        // Para evitar eso, lo que hacemos es que se rendericen las
                        // caras traseras, ya que lo que ocurre, al cambiar de ejes, 
                        // es que se cambia el sentido que define la cara frontal y trasera.
                        pVideoDriver->SetCullFaces( eCullFront );
                    }

                    // v0
                    vertexArray[0].color.rgba= rgbColor;

                    vertexArray[0].su= m_WidthChar*xOffset/m_WidthBitmap;
                    vertexArray[0].tv= 1.0f-m_HeightChar*yOffset/m_HeightBitmap;

                    vertexArray[0].x= x + m_WidthChar*i;
                    vertexArray[0].y= y + m_HeightChar;
                    vertexArray[0].z= 0.0f;

                    // v1
                    vertexArray[1].color.rgba= rgbColor;

                    vertexArray[1].su= m_WidthChar*xOffset/m_WidthBitmap;
                    vertexArray[1].tv= 1.0f-m_HeightChar*(yOffset+1)/m_HeightBitmap;

                    vertexArray[1].x= x + m_WidthChar*i;
                    vertexArray[1].y= y;
                    vertexArray[1].z= 0.0f;

                    // v2
                    vertexArray[2].color.rgba= rgbColor;

                    vertexArray[2].su= m_WidthChar*(xOffset+1)/m_WidthBitmap;
                    vertexArray[2].tv= 1.0f-m_HeightChar*yOffset/m_HeightBitmap;

                    vertexArray[2].x= x + m_WidthChar*(i+1);
                    vertexArray[2].y= y + m_HeightChar;
                    vertexArray[2].z= 0.0f;

                    // v3
                    vertexArray[3].color.rgba= rgbColor;

                    vertexArray[3].su= m_WidthChar*(xOffset+1)/m_WidthBitmap;
                    vertexArray[3].tv= 1.0f-m_HeightChar*(yOffset+1)/m_HeightBitmap;

                    vertexArray[3].x= x + m_WidthChar*(i+1);
                    vertexArray[3].y= y;
                    vertexArray[3].z= 0.0f;

                    pVideoDriver->RenderVertexArray( eTriangleStrip, (BYTE*)vertexArray,
                                                     sizeof(VertexFont2D), 0, 4,
                                                     eFVF_XYZ|eFVF_DIFFUSE|eFVF_TEX1 );
                }

                pVideoDriver->EnableBlending( false );
            }
        }
    }

En la aplicación principal, cargamos la textura del texto.


    ///////////////////////////////////////////////////////////////////////////////
    ///     OnCreateWindow: Función invocada al iniciarse el programa y DESPUÉS de crear la ventana.
    ///
    ///     @param  nada
    ///
    ///     @return  nada
    ///////////////////////////////////////////////////////////////////////////////
    void GraphicApplication::OnCreateWindow( WindowProps windowProps )
    {
        ulColorRGBA colorKey;
        colorKey.rgba= COLOR_RGBA(0x00,0x00,0x00,0xff); // Negro

        if ( m_pVideoDriver )
        {
            m_pVideoDriver->Init( windowProps );

            // Revisad las rutas si no cargasen las texturas
    // ********************************* NEW **************************************
            m_2DFont.LoadTexture(
    #if defined (_MSC_VER)
                "../../textures/TABLA-ASCII-160x713x24.bmp",
    #else
                "../../../textures/TABLA-ASCII-160x713x24.bmp",
    #endif
                WIDTH_BITMAP_TEXT, HEIGHT_BITMAP_TEXT, 10, 230, colorKey );
    // ****************************************************************************
        }
    }

Fijaros como al definir la proyeción ortogonal (2D), hemos desplazado el origen de coordenadas a la esquina superior izquierda. Esto supone, que para pintar texto en pantalla, la coordenada x toma valores positivos (de izquierda a derecha) pero la coordenada vertical y, toma valores NEGATIVOS.


    ///////////////////////////////////////////////////////////////////////////////
    ///     OnRender: Esta función es invocada cuando el sistema está listo para renderizar.
    ///
    ///     @param  nada
    ///
    ///     @return  nada
    ///////////////////////////////////////////////////////////////////////////////
    void GraphicApplication::OnRender()
    {
        Matrix mIdentity;
        fColorRGBA color = { 0.0f, 0.0f, 0.8f, 1.0f };  // Color de fondo.

        if ( m_pVideoDriver )
        {
            // Limpiamos la pantalla.
            m_pVideoDriver->ClearColor( color );
            m_pVideoDriver->ClearZBuffer();

            // Eje de coordenadas en la esquina superior izquierda
            m_pVideoDriver->SetOrthoProjection( 0.0f, m_WindowProps.width,      // left, right
                                                -m_WindowProps.height, 0.0f ,   // bottom, top
                                                -1.0f, 1.0f );
            m_pVideoDriver->BeginRender();

            if ( m_bWireframe )
            {
                m_pVideoDriver->SetRenderMode( eWireFrame );    // Renderizado wireframe
                m_pVideoDriver->SetCullFaces ( eCullNone  );    // Desactivamos la selección de caras a renderizar.
            }
            else
            {
                m_pVideoDriver->SetRenderMode( eSolid     );    // Renderizado sólido.
                m_pVideoDriver->SetCullFaces ( eCullBack  );    // No se renderizarán las caras traseras.
            }

            m_pVideoDriver->SetTransform( mIdentity );          // Resteamos las transformaciones previas.

            RenderObjects();

            m_pVideoDriver->EndRender();
        }
    }

Y en el método RenderObjects() dibujamos el texto que queramos.


    ///////////////////////////////////////////////////////////////////////////////
    ///     RenderObjects: Dibuja las geometrías.
    ///
    ///     @param  nada
    ///
    ///     @return  nada
    ///////////////////////////////////////////////////////////////////////////////
    void GraphicApplication::RenderObjects()
    {
    // ********************************* NEW **************************************

        Matrix mTransform, mResize, mTranslate;

        m_pVideoDriver->EnableLighting( false );
        m_pVideoDriver->EnableTexture ( true  );

        m_2DFont.SetTexture();

        // Amarilla
        m_2DFont.Render2DText( 30, -100, WINDOW_TITLE, COLOR_RGBA(0xff,0xff,0x00,0xff) );

        // Roja
        mTransform   = mResize.Scale(Vector3(1,3,0));
        m_pVideoDriver->SetTransform( mTransform );

        m_2DFont.Render2DText( 30, -2*m_2DFont.GetHeigthChar(), WINDOW_TITLE, COLOR_RGBA(0xff,0x00,0x00,0xff) );

        // Verde
        mTransform   = mTranslate.TranslateY( -5*m_2DFont.GetHeigthChar() );
        mTransform  *= mResize.Scale(Vector3(1.5f,1.5f,0));
        m_pVideoDriver->SetTransform( mTransform );

        m_2DFont.Render2DText( 30, 0, WINDOW_TITLE, COLOR_RGBA(0x00,0xff,0x00,0xff) );

    // ****************************************************************************
    }

Otra cosa, que debéis fijaros, como mediante el uso de matrices, podemos desplazar la fuente y cambiar su tamaño. Muestro el mismo texto tres veces para que veáis cómo debe hacerse para cambiar su color, posición o tamaño.

Fijaros también, como se realiza una única llamada a SetTexture() para renderizar los tres textos ya que es la misma para todos y así ganamos en eficiencia.

Podéis descargaros el ejemplo: tutorial-22.zip



Compartir en Facebook



Este artículo proviene de Programacion Grafica: Desarrollo 2D/3D con C++ y DirectX/OpenGL/GLUT/SDL - Windows/Linux
http://www.programaciongrafica.com

La dirección de esta noticia es:
http://www.programaciongrafica.com/modules.php?name=News&file=article&sid=25