Assuming that you have read Drawing 2d text

Here is a WebGL example showing what’s taking place in Sketch.h and Sketch.cpp from the Drawing3dText project’s source folder.

Sketch.cpp

Inside setup()

auto lines = utils::readLines<u16string>(InputSource::resource("song.txt"));

u16string text;
for (const auto &line : lines)
{
  text += line + u" ";
}

We read the text of the song as separate lines. Then we assemble a u16string using all the lines, separated by a space.

Then we load our font and declare that we’re going to use it for 3d.

After that, we compute the right offset for the text to be vertically centered on the helix:

float L = TWO_PI * TURNS * R;
float offset = (L - font->getStringAdvance(text)) / 2;

Finally, we create a FontSequence:

font->beginSequence(sequence);
drawTextHelix(*font, text, R, TURNS, -H, offset, XFont::ALIGN_MIDDLE);
font->endSequence();

Note that this is done only once, i.e. it is a static sequence. The effect of rotation comes from the camera.

Let’s take a look at the drawTextHelix() function:

void Sketch::drawTextHelix(XFont &font, const u16string &text, float r, float turns, float h, float offset, XFont::Alignment alignY)
{
  float l = TWO_PI * turns;
  float L = l * r;
  float dz = h / l;
  float ay = -atan2f(h, L);

  float offsetY = font.getOffsetY(alignY);
  Matrix matrix;

  for (auto c : text)
  {
    auto glyphIndex = font.getGlyphIndex(c);
    float halfWidth = font.getGlyphAdvance(glyphIndex) / 2;
    offset += halfWidth;

    if (glyphIndex >= 0)
    {
      float d = offset / r;
      matrix
        .setTranslate(-sinf(-d) * r, +cosf(-d) * r, d * dz)
        .rotateZ(-d)
        .rotateY(ay)
        .rotateX(-HALF_PI);

      font.addGlyph(matrix, glyphIndex, -halfWidth, offsetY);
    }

    offset += halfWidth;
  }
}

A cylindrical hexlix is defined by a radius, a number of turns and a height. For any offset on the helix, it’s possible to calculate linearly the right position and angle.

As usual, we use a 4x4 Matrix. For each character in the text, we translate it to the right offset on the helix and rotate it to the right angles. The idea of using halfWidth is to properly draw the glyph from its horizontal center.

Inside draw()

glCullFace(GL_FRONT);
font->setColor(1, 1, 1, 0.333f);
font->replaySequence(sequence);

glCullFace(GL_BACK);
font->setColor(1, 1, 1, 1);
font->replaySequence(sequence);

We draw our sequence 2 times. The first time, we exclude the front faces and draw the back faces in gray. The second time, we exclude the back faces and draw the front faces in white.