Assuming that you have read Drawing a 2d scene

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

If you run the demo, you can see a B-spline curve with 5 control points. It’s possible to move the control points via the mouse or via the arrow keys. The arrow keys will move the selected control point by 1 pixel or by 10 pixels if SHIFT is pressed.

Sketch.h

We can see 3 new functions: mouseDragged(), mousePressed() and keyDown(). These are the input functions. They will be explained later.

There is also a new kind of stock shader: TextureAlphaShader that will be explained later as well.

Sketch.cpp

Inside setup()

dotTexture = Texture(
  Texture::ImageRequest("dot_112.png")
    .setFlags(image::FLAGS_TRANSLUCENT)
    .setMipmap(true));

dotBatch
  .setShader(textureAlphaShader)
  .setTexture(dotTexture);

If you take a look at dot_112.png, you will see a white dot on a black background. By setting the TRANSLUCENT flag when loading the texture, we ensure that it will be uploaded to the GPU in GL_ALPHA format. Then, if we use a TextureAlphaShader for rendering our texture and, say, the current color is red: we will get a red dot. Note that the image must be a PNG saved in 8-bit mode.

points.emplace_back(100, 300);
points.emplace_back(300, 100);
points.emplace_back(500, 500);
points.emplace_back(700, 100);
points.emplace_back(900, 300);

We populate the points vector. That will be our control points. Then we call updateSpline():

SplinePath<glm::vec2> spline;
spline
  .setType(SplinePath<glm::vec2>::TYPE_BSPLINE)
  .setSamplingTolerance(16);

for (const auto &point : points)
{
  spline.add(point);
}

drawPolyline(spline.getPolyline());
updateDots();

We define a SplinePath of type BSPLINE, with a sampling tolerance of 16 (it affects the number of points that will be generated when sampling the spline.) Then, we add our control points to the spline and call drawingPolyline() with the points generated by the sampling of the curve. Finally, we call updateDots().

The drawPolyline() function is easy to understand. updateDots() is trivial as well (we iterate over all the control points and draw them in black, unless the point is selected, in which case it is drawn in red.) Let’s examine drawDot():

void Sketch::drawDot(const glm::vec2 &position, const glm::vec4 &color, float radius)
{
  static constexpr float DOT_RADIUS_PIXELS = 56; // Specific to "dot_112.png"

  draw::Sprite()
    .setAnchor(0.5f, 0.5f)
    .setColor(color)
    .append(dotBatch, Matrix()
      .translate(position)
      .scale(radius / DOT_RADIUS_PIXELS));
}

We draw our dot using a Sprite drawn from its center, with the required color and placed at the required position, with a scale factor. This factor ensure that the dot in the original image (which has a radius of 56 pixels) is scaled to the required radius.

Inside draw()

Matrix viewMatrix;
viewMatrix
  .translate(0, windowInfo.height)
  .scale(1, -1);

We create a view matrix with an origin at the top-left of the window and with a Y axis going down.

The input functions

void Sketch::mousePressed(int button, float x, float y)
{
  selectedIndex = getClosestPointIndex(x, y);
  updateDots();
}

When the mouse is pressed, we test if one of the control points is close enough in which case we select it.

void Sketch::mouseDragged(int button, float x, float y)
{
  if (selectedIndex != -1)
  {
    points[selectedIndex] = glm::vec2(x, y);
    updateSpline();
  }
}

When the mouse is dragged and a point is selected, we update its position.

void Sketch::keyDown(int keyCode, int modifiers)
{
  if (selectedIndex != -1)
  {
    float distance = (modifiers & KeyEvent::MODIFIER_SHIFT) ? 10 : 1;

    switch (keyCode)
    {
      case KEY_LEFT:
        points[selectedIndex] += glm::vec2(-distance, 0);
        break;

      case KEY_RIGHT:
        points[selectedIndex] += glm::vec2(+distance, 0);
        break;

      case KEY_UP:
        points[selectedIndex] += glm::vec2(0, -distance);
        break;

      case KEY_DOWN:
        points[selectedIndex] += glm::vec2(0, +distance);
        break;

      default:
        return;
    }

    updateSpline();
  }
}

If SHIFT is pressed, we move by 10 pixels, otherwise we move by 1 pixel.