At the end of the Moving The Circle Off Centre post, the `CirclesAndRotators`

program displayed a circle located at pixel coordinates (220, -150) relative to the centre of the canvas. Before adding a second circle, we will refactor the program by adding a `GlCircle`

class that encapsulates the the properties and functionality of circles in OpenGL, and then we will add a rotational transformation to the circle. Finally, we will add a second rotating circle.

## Refactoring To Use A GlCircle Class

### The GlCircleClass

In OpenGL, there are three tasks:

- Initialization;
- Drawing; and,
- Cleanup.

These map nicely in the `GlCircle`

class to the constructor, the `Paint`

method and the destructor. The simplest way to create a circle is to define it centred at the origin and then transform it to its desired location. This is the order in which you would do things in games and other programs where objects move. So, with the centre of the circle specified for all circles, the simplest way to define a circle is in terms of its radius. Two other arguments are required in the call to the constructor; these are the transformation value between object coordinates and device coordinates, and the shader program. You will see in a moment where the shader program is used.

In the `CirclesAndRotators`

program, pixels are used as object coordinates, with the origin at the centre of the canvas that is drawn on. This means that the transformation value between object coordinates and device coordinates is simply the fourth value in 4D space (`w`

).

The `GlCircle`

class constructor will create the vertex array, buffers, and buffer data required by the GPU. This code is very much a simple copy of the code that was in `CirclesAndRotatorsCanvas::CreateSquareForCircle`

and `CirclesAndRotatorsCanvas::BuildCircleShaderProgram`

. Here is the `GlCircle`

constructor:

GlCircle::GlCircle(float radius, float w, GLuint shaderProgram) : m_radius(radius), m_w(w) { // create vertices for circle centred at origin glm::vec4 vertices[] = { { -m_radius, -m_radius, 0.0f, w }, { m_radius, -m_radius, 0.0f, w }, { m_radius, m_radius, 0.0f, w }, { -m_radius, m_radius, 0.0f, w } }; // create elements that specify the vertices. GLint elements[6] = { 0, 1, 2, 2, 3, 0 }; // circle is centred at the origin. Later, in the Paint method, we transform the location to // where we want it. // VAO glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); // upload vertex data glGenBuffers(1, &m_vbo); glBindBuffer(GL_ARRAY_BUFFER, m_vbo); glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), vertices, GL_STATIC_DRAW); // upload element data glGenBuffers(1, &m_ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(GLint), elements, GL_STATIC_DRAW); // set up position attribute used in circle vertex shader GLint posAttrib = glGetAttribLocation(shaderProgram, "position"); glEnableVertexAttribArray(posAttrib); glVertexAttribPointer(posAttrib, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); // set up the uniform arguments m_outerRadius = glGetUniformLocation(shaderProgram, "outerRadius"); m_viewDimensions = glGetUniformLocation(shaderProgram, "viewDimensions"); m_transform = glGetUniformLocation(shaderProgram, "transform"); glBindVertexArray(0); // unbind the VAO }

The only things of note are:

- The vertices for the square containing the circle are defined in terms of the radius of the circle;
- A separate set of vertex arrays and buffers are created for each circle; and,
- The vertex array object is unbound at the end of the destructor.

Unbinding the vertex array object ensures that the buffer contents are not accidentally altered outside the constructor.

The destructor simply deletes the vertex array object, the vertex buffer, and elements buffer.

The `Paint`

method is straight-forward:

void GlCircle::Paint(glm::mat4& transform) const noexcept { glBindVertexArray(m_vao); glUniformMatrix4fv(m_transform, 1, GL_FALSE, glm::value_ptr(transform)); // set outer radius for circle here. We will be modulating it in later // example glUniform1f(m_outerRadius, m_radius); glUniform2f(m_viewDimensions, 2.0f * m_w, 2.0f * m_w); // draw the square that will contain the circle. // The circle is created inside the square in the circle fragment shader glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glBindVertexArray(0); // unbind the VAO }

In the `Paint`

method, the vertex array for the circle is bound, the uniform values are transferred to the shader program, the circle is drawn, and the vertex array is unbound.

Here is the `GlCircle`

class declaration:

#pragma once #include "GL/glew.h" #include "wx/glcanvas.h" #define GLM_FORCE_CXX14 #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> class GlCircle { public: GlCircle(float radius, float w, GLuint shaderProgram); virtual ~GlCircle() noexcept; void Paint(glm::mat4& transform) const noexcept; private: float m_radius; float m_w; GLuint m_vao; GLuint m_vbo; GLuint m_ebo; GLint m_transform; GLint m_viewDimensions; GLint m_outerRadius; };

### Using the GlCircle Class

The `CirclesAndRotatorsCanvas::CreateSquareForCircle`

method is renamed to `CreateCircles`

, and is simplified to:

void CirclesAndRotatorsCanvas::CreateCircles() { // define vertices for the two triangles wxSize canvasSize = GetSize(); float w = static_cast(canvasSize.x) / 2.0f; m_circle1 = std::make_unique(80.0f, static_cast(canvasSize.x) / 2.0f, m_circleShaderProgram); }

The `BuildCircleShaderProgram`

method is also simplified to:

void CirclesAndRotatorsCanvas::BuildCircleShaderProgram() { // build the circle shaders BuildCircleVertexShader(); BuildCircleFragmentShader(); // create and link circle shader program m_circleShaderProgram = glCreateProgram(); glAttachShader(m_circleShaderProgram, m_circleVertexShader); glAttachShader(m_circleShaderProgram, m_circleFragmentShader); glBindFragDataLocation(m_circleShaderProgram, 0, "outColor"); glLinkProgram(m_circleShaderProgram); }

Note that the code that used the shader program, but that was not actually related to building the shader program has been removed.

The `OnPaint`

method is modified to:

void CirclesAndRotatorsCanvas::OnPaint(wxPaintEvent& event) { // set background to black glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // use the circleShaderProgram glUseProgram(m_circleShaderProgram); // set the transform wxSize canvasSize = GetSize(); float w = static_cast(canvasSize.x) / 2.0f; glm::mat4 transform; transform = glm::translate(transform, glm::vec3(220.0f / w, -150.0f / w, 0.0f / w)); m_circle1->Paint(transform); glFlush(); SwapBuffers(); }

With the changes made above, the order that the two methods are called in the `SetupGraphics`

method must be reversed. Also, a number of changes are required to the `#include`

‘s in `CirclesAndRotatorsCanvas`

and `CirclesAndRotatorsFrame`

source files.

Building and running the program should result in the circle being displayed at (220, -150) as before.

## Rotational Transformation

To add a rotation to the circle, replace the call to paint the circle in `CirclesAndRotatorsCanvas::OnPaint`

with:

glm::mat4 rotation; rotation = glm::rotate(rotation, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 transrotate; transrotate = rotation * transform; m_circle1->Paint(transrotate);

In this code, we create two new matrices: `rotation`

, and `transrotate`

The `rotate`

function is called to create a transformation matrix that rotates an object about the z-axis by 180 degrees. The transrotate matrix is the result of multiplying the translation (`transform`

) matrix by the `rotation`

matrix. Note that the translation is performed first and then the rotation.

Build and run the program. This is the result:

The circle was translated to (220, -150) then rotated about the centre of the canvas in a counter-clockwise direction 180° to (-220, 150).

Matrix multiplication is not transitive; that is,

transrotate = rotation * transform;

does not produce the same result as

transrotate = transform * rotation;

In this latter case, the rotation would be performed first and then the translation, but since the circle is initially centred at (0, 0), applying the rotation matrix simply rotates the circle about (0, 0), producing no visual change. The translation then moves the rotated circle to (220, -150).

## Varying the Rotation Over Time

To vary the position of the circle over time, we need to create a timer that calls the `CirclesAndRotatorsCanvas::OnPaint`

method, and in that method, calculate the rotation based on the time. Because I have previously described the changes needed to create and fire the timer, I will not repeat it here. See the Varying the Colour of the Triangle section of the OpenGL Shaders post for a description of the changes required to call the `OnPaint`

method every time the timer fires.

In `OnPaint`

, replace the line:

rotation = glm::rotate(rotation, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f));

with:

auto t_now = std::chrono::high_resolution_clock::now(); auto time = (t_now - m_startTime).count(); rotation = glm::rotate(rotation, time * 2.5e-10f, glm::vec3(0.0f, 0.0f, 1.0f));

The angle of rotation is now calculated based on the time since the `CircleAndRotatorsCanvas`

object was created. The angle is calculated in radians, and the value `2.5e-10`

was selected to provide a slow rate of rotation. Build and run the program to see the result.

## Adding A Second Circle

We will be adding a second circle that rotates over top of the first circle. The fragment shader hard-codes the colour of the circle to green. In order to see the second circle as it rotates over the first, we need to set different colours for the two circles. Change the fragment shader program to accept the colour as a `uniform vec4`

, and modify the code in `OnPaint`

and `GlCircle::Paint`

to set this value.

In `CirclesAndRotatorsCanvas`

, add a second `GlCircle`

called `m_circle2`

, and add:

m_circle2 = std::make_unique(30.0f, w, m_circleShaderProgram);

after the definition of `m_circle1`

in CirclesAndRotatorsCanvas::CreateCircles.

Now, in `OnPaint`

, after the call to `m_circle1->Paint`

,

add the following:

transform = glm::mat4(); transform = glm::translate(transform, glm::vec3(-200.0f / w, -75.0f / w, 0.0f / w)); rotation = glm::rotate(rotation, time * 5e-10f, glm::vec3(0.0f, 0.0f, 1.0f)); transrotate = rotation * transform; m_circle2->Paint(transrotate, glm::vec4(1.0f, 0.5f, 0.0f, 1.0f));

This creates and displays a smaller, orange circle that rotates twice as quickly as the blue circle. See this video for the result:

The code for this program is located in the rotate branch on GitHub.