Assuming that you have read Working with input and splines

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

Sketch.h

We can see a new type: FollowablePath2D. Each time a new point is added to it, the distance from that point to the previous one is stored internally. Then, using binary search, it’s possible to figure out the 2d position of a point on the path by passing a distance from the beginning of the path.

Sketh.cpp

Inside setup()

We can see that lineBatch is using a new kind of primitive: GL_LINES. It takes 2 vertices at a time, and draws a segment between them. We can’t use GL_LINE_STRIP because we are drawing 3 polylines with the same batch.

Then we start working with FollowablePath2D:

path1
  .setMode(FollowablePath2D::MODE_LOOP)
  .begin()
  .add(450, 300)
  .add(500, 300)
  .add(500, 350)
  .add(450, 350)
  .end(true);

drawPolyline(path1.getPoints());

for (const auto &point : path1.getPoints())
{
  drawDot(point.position, 2);
}

We create a square-shaped path and we close it by passing true when calling end(). We use the mode LOOP which defines that we will keep moving around the closed path.

Then we draw a polyline made of the path’s points, and a dot for each point.

The next chunk defines and draws an open path, in mode MODULO which defines that motion will start from the beginning when the end of the path is reached.

Then we start with SplinePath:

MatrixAffine matrix;
matrix
  .translate(150, 150)
  .rotate(-45 * D2R);

SplinePath<glm::vec2> peanut;
peanut
  .setType(SplinePath<glm::vec2>::TYPE_BSPLINE)
  .setSamplingTolerance(16)
  .add(-100, -100)
  .add(   0,  -25)
  .add( 100, -100)
  .add( 200,    0)
  .add( 100,  100)
  .add(   0,   25)
  .add(-100,  100)
  .add(-200,    0)
  .close()
  .transformPoints(matrix);

path3
  .setMode(FollowablePath2D::MODE_LOOP)
  .begin()
  .add(peanut.getPolyline())
  .end();

drawPolyline(path3.getPoints());

for (const auto &point : path3.getPoints())
{
  drawDot(point.position, 2);
}

We create a peanut-shaped B-spline which we transform via a MatrixAffine so that the curve is translated and rotated. Then we feed a FollowablePath2D with the curve.

Inside draw()

Matrix matrix1, matrix2, matrix3;

path1
  .offsetToValue(clock()->getTime() * 40, 10)
  .applyToMatrix(matrix1);

path2
  .offsetToValue(clock()->getTime() * -50, 20)
  .applyToMatrix(matrix2);

path3
  .offsetToValue(clock()->getTime() * -30, 15)
  .applyToMatrix(matrix3);

For our 3 paths, we call offsetToValue() with 2 parameters. The first one is the offset (or distance) on the path and the second is the sample-size, which is related to the size of the object that we’re going to animate on the path. Then, this function returns a value, which is a structure containing information about the 2d position and the rotation at the specified offset. Finally, we apply this value to a 4x4 matrix.

draw::Rect()
  .setColor(0, 0.50f, 0.75f, 0.75f)
  .setBounds(-5, -5, 10, 10)
  .append(flatBatch, matrix1);

draw::Rect()
  .setColor(0, 0.75f, 0.25f, 0.75f)
  .setBounds(-10, -10, 20, 20)
  .append(flatBatch, matrix2);

draw::Rect()
  .setColor(1, 0.5f, 0.25f, 0.75f)
  .setBounds(-7.5f, -7.5f, 15, 15)
  .append(flatBatch, matrix3);

Then we draw 3 rectangles. Each of them is transformed by the matrix previously computed. flatBatch has vertices of type XYZ.RGBA so we can have a different color per rectangle. Finally, setBounds() accepts 4 parameters: x, y, width and height.