Assuming that you have read Drawing a desert

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

If you run the experiment, you can see a landscape represented by one single continuous line. The height coordinates for the terrain are generated using Perlin Noise which can produce a unique configuration for each seed number.

Clicking on screen will change the seed number based on mouse coordinates, resulting in a new terrain generation.

Sketch.h

We can see one new class: NoiseSurfaceSpiral2.

NoiseSurfaceSpiral2

Have a look at NoiseSurfaceSpiral2.h and NoiseSurfaceSpiral2.cpp. This class is capable of tracing a spiral-shaped FollowablePath3D on top of a NoiseSurface. In addition, it can fill the vertices of a vertex-buffer it holds internally. For smoother results, it is using internally a 3d B-spline.

FollowablePath3D is similar to FollowablePath2D, except that the add() method must be fed with a 3d coordinate and a 3d vector marking the left direction. The forward direction is automatically calculated when adding points. Finally, the up direction is inferred upon evaluation of the path using the cross product of the left and forward directions.

Sketch.cpp

Inside setup()

auto lines = utils::readLines<u16string>(InputSource::resource("Isaiah_40.txt"));
for (const auto &line : lines)
{
  text += line + u" ";
}

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

Then we load our font and declare a negative direction, since the text is in Hebrew:

font = fontManager.getFont(InputSource::resource("FrankRuehl_Regular_64.fnt"), XFont::Properties3d());
font->setDirection(-1);

Then we configure our NoiseSurfaceSpiral2:

spiral.enableWire(true);

The vertices of the internal vertex-buffer will be filled.

spiral.enablePath(true);

A FollowablePath3D will be traced.

spiral.setSampleSize(1.5f);

This value will be used when tracing the FollowablePath, specifically when computing the left direction.

spiral.setSamplingTolerance(0.0025f);

The lower this value is, the more segments will be created when sampling the B-spline.

spiral.path.setSampling(FollowablePath3D::SAMPLING_CONTINUOUS);

When moving on the path, it will be evaluated in a less coarse fashion.

spiral.setup(R1, R2, TURNS, DD1, DD2);

Finally, we generate the points making the spiral, without the height information.

Then, we precompute the internal table of the NoiseSurface:

surface.generateBase();

And we call generateSpiral():

spiral.update(surface, TERRAIN_H);

float offset = 0;
float offsetY = font->getOffsetY(XFont::ALIGN_MIDDLE);
Matrix matrix;

font->beginSequence(sequence);

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

  if (glyphIndex >= 0)
  {
    spiral.path
      .offsetToValue(offset, halfWidth * 2)
      .applyToMatrix(matrix);

    if (font->getDirection() < 0)
    {
      matrix.rotateZ(PI);
    }

    font->addGlyph(matrix, glyphIndex, -halfWidth * font->getDirection(), offsetY);
  }

  offset += halfWidth;
}

font->endSequence();

The first operation will fill the internal vertex-buffer used for drawing the spiral’s line, and trace the FollowablePath3D used for drawing the text.

The rest should be clear to you, otherwise you can refer to Drawing text on 2d path.

Inside draw()

This is similar to what’s taking place in Working with noise.

The input functions

void Sketch::mouseDragged(int button, float x, float y)
{
  mousePressed(button, x, y);
}
void Sketch::mousePressed(int button, float x, float y)
{
  reseed(x * y);
}

Each time the mouse is pressed or dragged, we call reseed() by passing a seed number corresponding to the position on screen:

void Sketch::reseed(int64_t seed)
{
  surface.reseed(seed);
  generateSpiral();
}