TTFTriangulator demo

posted in DevJournal for project QLMesh 2.0
Published July 30, 2018
Advertisement

I have updated demo for TTFTriangulator (simple C++ library designed to load a truetype font and triangulate its glyphs in real time) library to something working (I think). It is a little chaotic, but also very simple and generic, so easy to reuse (btw: it uses Qt for I/O and windows creation, but you can also use the code I modified and that is using GFLW).

You can use my amalgamated (and dependency-free) version, available here:


-rw-r--r--  1 piecuchp  staff    69K Jul 26 17:48 TTF.cpp
-rwxr-xr-x  1 piecuchp  staff    93K Jul 26 06:45 TTF.h
-rw-r--r--  1 piecuchp  staff   217K Jul 26 06:50 TTF.o

(it is also modified to use QFile for I/O so you can work with Qt's embedded resources)


#include <QDebug>
#include <QElapsedTimer>
#include <QMouseEvent>
#include <QPainter>
#include <QWindow>
#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLPaintDevice>
#include <QOpenGLFunctions>
#include <QApplication>

#include "TTF.h"
using namespace TTF;


const char *fragCodeSimple = "                                  \n\
varying vec3 tpos;                                              \n\
float round(float val)                                          \n\
{                                                               \n\
    return sign(val)*floor(abs(val)+0.5);                       \n\
}                                                               \n\
void main()                                                     \n\
{                                                               \n\
    float alpha = round((tpos.x*tpos.x-tpos.y)*tpos.z+0.5);     \n\
    gl_FragColor = alpha *vec4(1.0,1.0,1.0,1.0);                \n\
}                                                               \n\
";


const char *fragCode ="                                         \n\
varying vec3 tpos;                                              \n\
void main()                                                     \n\
{                                                               \n\
    float alpha = 1.0;                                          \n\
    if (tpos.z != 0.0)                                          \n\
    {                                                           \n\
        vec2 p = tpos.xy;                                       \n\
        // Gradients                                            \n\
        vec2 px = dFdx(p);                                      \n\
        vec2 py = dFdy(p);                                      \n\
        // Chain rule                                           \n\
        float fx = ((2.0*p.x)*px.x-px.y);                       \n\
        float fy = ((2.0*p.x)*py.x-py.y);                       \n\
        // Signed distance                                      \n\
        float dist = fx*fx + fy*fy;                             \n\
        float sd = (p.x*p.x - p.y)*tpos.z/sqrt(dist);           \n\
        // Linear alpha                                         \n\
        alpha = 0.5 - sd;                                       \n\
        if (alpha < 0.0) // Outside                             \n\
            discard;                                            \n\
    }                                                           \n\
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);                    \n\
}                                                               \n\
";

const char *vertCode = "                                        \n\
attribute float t;                                              \n\
attribute float c;                                              \n\
attribute vec2 pos;                                             \n\
varying vec3 tpos;                                              \n\
void main(void)                                                 \n\
{                                                               \n\
    tpos = vec3(t*0.5, max(t-1.0, 0.0), c);                     \n\
    gl_Position = gl_ModelViewProjectionMatrix*vec4(pos, 0.0, 1.0);\n\
}                                                               \n\
";

double qtGetTime() {
    static QElapsedTimer timer;
    if (!timer.isValid())
    timer.start();
    return timer.elapsed() / 1000.;
}

class OpenGLWindow : public QWindow, public QOpenGLFunctions
{
    Q_OBJECT
    typedef void (^RenderBlock)();
private:
    bool m_done, m_update_pending, m_auto_refresh;
    QOpenGLContext *m_context;
    QOpenGLShaderProgram m_shader;
    Font m_f;
public:
    QPoint cursorPos;
public:
    OpenGLWindow(QWindow *parent = 0) : QWindow(parent)
        , m_update_pending(false)
        , m_auto_refresh(true)
        , m_context(0)
        , m_f(":/fonts/VinMonoPro-Light.ttf")
        , m_done(false) {
        setSurfaceType(QWindow::OpenGLSurface);
    }
    ~OpenGLWindow() { }
    
    void setAutoRefresh(bool a) { m_auto_refresh = a; }
    
    void initialize() {
        qDebug() << "OpenGL infos with gl functions:";
        qDebug() << "-------------------------------";
        qDebug() << " Renderer:" << (const char*)glGetString(GL_RENDERER);
        qDebug() << " Vendor:" << (const char*)glGetString(GL_VENDOR);
        qDebug() << " OpenGL Version:" << (const char*)glGetString(GL_VERSION);
        qDebug() << " GLSL Version:" << (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
        
        m_shader.addShaderFromSourceCode(QOpenGLShader::Vertex, vertCode);
        m_shader.addShaderFromSourceCode(QOpenGLShader::Fragment, fragCode);
        m_shader.link();
    }
    void update() { renderLater(); }
    void render() {
        glViewport(0, 0, width()*devicePixelRatio(), height()*devicePixelRatio());
        glClearColor(0.8, 0.8, 0.8, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_CULL_FACE);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-1, 1, -1, 1, -10, 10);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        m_shader.bind();

        float scale = 0.1 + 0.2*fabs(cos(qtGetTime()/2.0));
        glScalef(scale, scale, 1);
        glTranslatef(-3.6, 0, 0);

        renderMsg(m_f, "KomSoft");
        m_shader.release();
    }
    void mousePressEvent(QMouseEvent *event) {
        cursorPos = QPoint(event->x(), event->y());
        Qt::KeyboardModifiers modifiers = event->modifiers();
        if (event->buttons() & Qt::LeftButton) { }
    }
    void mouseReleaseEvent(QMouseEvent *event) {
        cursorPos = QPoint(event->x(), event->y());
        Qt::KeyboardModifiers modifiers = event->modifiers();
        if (event->button() == Qt::LeftButton) { }
    }
    void mouseMoveEvent(QMouseEvent *event) {
        cursorPos = QPoint(event->x(), event->y());
    }
    void keyPressEvent(QKeyEvent* event) {
        switch(event->key()) {
        case Qt::Key_Escape: quit(); break;
        default: event->ignore();
            break;
        }
    }
    void quit() { m_done = true; }
    bool done() { return m_done; }
    protected:
    void closeEvent(QCloseEvent *event) { quit(); }
    bool event(QEvent *event) {
        switch (event->type()) {
        case QEvent::UpdateRequest:
            m_update_pending = false;
            renderNow();
            return true;
        default:
            return QWindow::event(event);
        }
    }
    void exposeEvent(QExposeEvent *event) {
        Q_UNUSED(event);
        if (isExposed()) renderNow();
    }
    
    public slots:
    void renderLater() {
        if (!m_update_pending) {
            m_update_pending = true;
            QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
        }
    }
    void renderNow() {
        if (!isExposed()) return;
        bool needsInitialize = false;
        if (!m_context) {
            m_context = new QOpenGLContext(this);
            m_context->setFormat(requestedFormat());
            m_context->create();
            needsInitialize = true;
        }
        m_context->makeCurrent(this);
        if (needsInitialize) {
            initializeOpenGLFunctions();
            initialize();
        }
        render();
        m_context->swapBuffers(this);
        if (m_auto_refresh) renderLater();
    }
    private:
        void renderMsg(const Font &f, const char *msg);
};

void OpenGLWindow::renderMsg(const Font &f, const char *msg)
{
    TTF::FontMetrics font_metrics = f.GetFontMetrics(); // will tell you about the font
    // Triangulator2DI, Triangulator2DII, Triangulator2DLinearI, Triangulator2DLinearII
    TTF::Triangulator2DI triangulator;
    for (int i = 0; i < strlen(msg); i++)
    {
        CodePoint cp(msg[i]);
        f.TriangulateGlyph(cp, triangulator);
    
        if (i > 0)
        {
            TTFCore::vec2t kerning = f.GetKerning(CodePoint(msg[i-1]), cp);
            glTranslatef(0.9*kerning.x*0.001, kerning.y*0.001, 0);
        }

        struct vertex_t
        {
            vec2f pos;
            signed char texCoord; // 0 = (0,0), 1 = (0.5,0), 2 = (1,1)
            signed char coef;     // -1 = CW edge, 0 = inner segment, +1 = CCW segment
        };
        QVector<vertex_t> verts;

        for (auto tri : triangulator) {
            TTF::vec2t v0 = triangulator[tri.i0];
            TTF::vec2t v1 = triangulator[tri.i1];
            TTF::vec2t v2 = triangulator[tri.i2];
            // store in a buffer, or do something with it from here... up to you really
            verts.push_back((vertex_t){{0.001f*v0.x, 0.001f*v0.y}, 0, static_cast<signed char>(tri.coef)});
            verts.push_back((vertex_t){{0.001f*v1.x, 0.001f*v1.y}, 1, static_cast<signed char>(tri.coef)});
            verts.push_back((vertex_t){{0.001f*v2.x, 0.001f*v2.y}, 2, static_cast<signed char>(tri.coef)});
        }

        if (verts.size())
        {
            GLint loc = m_shader.attributeLocation("t");
            glEnableVertexAttribArray(loc);
            glVertexAttribPointer(loc, 1, GL_BYTE, GL_FALSE, sizeof(vertex_t), &verts[0].texCoord);
            loc = m_shader.attributeLocation("c");
            glEnableVertexAttribArray(loc);
            glVertexAttribPointer(loc, 1, GL_BYTE, GL_FALSE, sizeof(vertex_t), &verts[0].coef);
            loc = m_shader.attributeLocation("pos");
            glEnableVertexAttribArray(loc);
            glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), &verts[0].pos);

            glDrawArrays(GL_TRIANGLES, 0, verts.size());
        }

#if 0
        printf("%c: %d verts\n", msg[i], verts.size());
        for (int j = 0; j < verts.size(); j++)
        {
            const vertex_t &mv = verts[j];
            printf("%c %d %d, %d: (%f, %f), %d, %d\n", msg[i], j, j/3, j%3, mv.pos.x, mv.pos.y, mv.texCoord, mv.coef);
        }
#endif
    }
}

int main(int argc, char *argv[])
{
    QSurfaceFormat surface_format = QSurfaceFormat::defaultFormat();
    surface_format.setAlphaBufferSize( 8 );
    surface_format.setDepthBufferSize( 24 );
    // surface_format.setRedBufferSize( 8 );
    // surface_format.setBlueBufferSize( 8 );
    // surface_format.setGreenBufferSize( 8 );
    // surface_format.setOption( QSurfaceFormat::DebugContext );
    // surface_format.setProfile( QSurfaceFormat::NoProfile );
    // surface_format.setRenderableType( QSurfaceFormat::OpenGLES );
    // surface_format.setSamples( 4 );
    // surface_format.setStencilBufferSize( 8 );
    // surface_format.setSwapBehavior( QSurfaceFormat::DefaultSwapBehavior );
    // surface_format.setSwapInterval( 1 );
    // surface_format.setVersion( 2, 0 );
    QSurfaceFormat::setDefaultFormat( surface_format );

    QApplication app(argc, argv);
    OpenGLWindow w;
    w.resize(800, 600);
    w.show();
    return app.exec();
}

#include "demo.moc"

 

1.jpg

2.jpg

0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement