Drawing a Cube made the cube tumble with one line. This chapter takes that line apart. You bring the cube into the spin program of Rotating a Triangle, where the keys already turn things, and try one axis at a time, then two turns at once, then the mouse. At the end, three cubes spin at their own speeds and a small moon circles the middle one.
By the end you can
- Predict which faces a cube shows when it turns around x, y or z.
- Combine two rotations and choose their order: a turntable or a wobble.
- Turn the cube with a mouse drag, using yaw and pitch.
- Give several objects their own spin, and make one orbit another.
It continues spin.cpp from the end of Rotating a Triangle: the triangle that spins, pauses with Space, turns by hand with Left and Right, and picks its axis with X, Y and Z. Your main.cpp is not touched. Run the program after each step; each step says what you should see.
- Step 1: the colour cube instead of the triangle, in perspective.
- Step 2: one axis at a time, with the keys you already have.
- Step 3: two turns at once, in both orders.
- Step 4: drag with the mouse to turn it.
- Step 5: three cubes and a moon.
Use the cube from step 1 of Drawing a Cube: 8 vertices with positions only, 36 indices, coloured by its own position. It has no texture, so spin.cpp needs no image and no stb_image. Replace the triangle's vertices in step 4b:
1float vertices[] = {
2// x y z
3 -0.5f, 0.5f, 0.5f, // 0: front top-left
4 -0.5f, -0.5f, 0.5f, // 1: front bottom-left
5 0.5f, -0.5f, 0.5f, // 2: front bottom-right
6 0.5f, 0.5f, 0.5f, // 3: front top-right
7 -0.5f, 0.5f, -0.5f, // 4: back top-left
8 -0.5f, -0.5f, -0.5f, // 5: back bottom-left
9 0.5f, -0.5f, -0.5f, // 6: back bottom-right
10 0.5f, 0.5f, -0.5f, // 7: back top-right
11};
12GLuint indices[] = {
13 0, 1, 2, 0, 2, 3, // front (+z)
14 7, 6, 5, 7, 5, 4, // back (−z)
15 3, 2, 6, 3, 6, 7, // right (+x)
16 4, 5, 1, 4, 1, 0, // left (−x)
17 4, 0, 3, 4, 3, 7, // top (+y)
18 1, 5, 6, 1, 6, 2, // bottom (−y)
19};
20GLuint vao, vbo, ebo; // NEW: ebo
21glGenVertexArrays(1, &vao);
22glGenBuffers(1, &vbo);
23glGenBuffers(1, &ebo); // NEW
24
25glBindVertexArray(vao);
26glBindBuffer(GL_ARRAY_BUFFER, vbo);
27glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
28glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); // stride: was 6
29glEnableVertexAttribArray(0);
30// attribute 1 (the colour) is gone: delete its two lines
31glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); // NEW, while the VAO is bound
32glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // NEW
33glBindVertexArray(0);The vertex shader loses aColor and computes the colour from the position instead. It also gets the view matrix from the Transformations + GLM chapter, between the other two:
1#version 460 core
2layout (location = 0) in vec3 aPos;
3out vec3 vColor;
4uniform mat4 uModel;
5uniform mat4 uView; // NEW
6uniform mat4 uProjection;
7void main() {
8 gl_Position = uProjection * uView * uModel * vec4(aPos, 1.0); // NEW: uView in the middle
9 vColor = aPos + 0.5; // NEW: −0.5…0.5 becomes 0…1
10}The fragment shader stays as it is. In step 4c, look up the new uniform and switch on the depth test, so the near faces hide the far ones:
1GLint viewLoc = glGetUniformLocation(shaderProgram, "uView"); // NEW
2glEnable(GL_DEPTH_TEST); // NEWLast, the loop. The flat glm::ortho gives way to glm::perspective, so the faces that come toward you get bigger, and the view matrix steps the camera 3 units back. Both are the ones from the Transformations + GLM chapter. The draw call reads 36 indices:
1glm::mat4 projection = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f); // was glm::ortho
2glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f)); // NEW
3glm::mat4 model = glm::rotate(glm::mat4(1.0f), angle, axis); // unchanged
4
5glUseProgram(shaderProgram);
6glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
7glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view)); // NEW
8glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
9glBindVertexArray(vao);
10glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0); // was glDrawArrays(…, 3)In step 6, delete the EBO too: glDeleteBuffers(1, &ebo). Your glClear already clears the depth buffer (GL_DEPTH_BUFFER_BIT), which the depth test needs.
There is nothing to type in this step: the X, Y and Z keys from the last chapter already pick the axis. Press each one and watch which faces come round. The figure does the same; pick an axis there and compare.
| Key | Axis | What you see | Never faces you |
|---|---|---|---|
| Y | (0, 1, 0) | a turntable. The front face slides off to the right and the left face comes round, then the back, then the right. | top and bottom |
| X | (1, 0, 0) | a wheel rolling toward you. The top face comes down to the front, then the back, then the bottom. | left and right |
| Z | (0, 0, 1) | the front face turning like a clock hand. It looks flat because the axis points straight at you. | all but the front |
So one axis always hides two faces. Press Y, and press Space when two faces share the picture equally, at 45°: the front face on the right, the left face on the left. Hold Left or Right to turn it by hand from there.
glm::mat4 model(1.0f); model = glm::rotate(model, glm::radians(25.0f), glm::vec3(0.0f, 1.0f, 0.0f));
Around y the cube turns like a turntable: the front face slides off to the right and the left face comes round. The top and bottom never face you.
To see the top while the cube spins around y, tilt it toward you as well. That is a second rotate. Replace the model line; the X, Y and Z keys and the axis variable are no longer used, so delete them:
1glm::mat4 model(1.0f);
2model = glm::rotate(model, glm::radians(30.0f), glm::vec3(1.0f, 0.0f, 0.0f)); // 2nd: tilt the top toward you
3model = glm::rotate(model, angle, glm::vec3(0.0f, 1.0f, 0.0f)); // 1st: spin around its own up axisAs always, the call written last acts first. So every frame the cube first spins around its own up axis, by angle, and then the whole spun cube is tipped 30° toward you. The tilt comes after the spin, so it is always the same: you look at a turntable from a little above, and the top face never leaves.
Now swap the two lines and run it again. The tilt acts first, and the spin carries the tilted cube round with it. After half a turn, the side that leaned toward you leans away, and you see the bottom face instead of the top. The cube wobbles, like a coin about to fall over. Same two calls, different order, different motion. In the figure, compare "tilt, then spin" with "spin, then tilt".
| Written order | Acts on the vertex | Motion |
|---|---|---|
| tilt, then spin | spin first, tilt last | a turntable seen from above; the top is always visible |
| spin, then tilt | tilt first, spin last | a wobble; top and bottom take turns |
The fixed 30° tilt is about to become something you drag. Two angles do it, with names from aircraft: yaw, the turn around the up axis (y), and pitch, the tip toward you or away from you (around x). Dragging right turns the cube right; dragging down tips its top toward you. That is how 3D editors such as Blender turn an object. Add them to the state in step 4, with the mouse position of the last frame:
1float yaw = 0.0f; // around y, set by dragging sideways
2float pitch = glm::radians(30.0f); // around x, set by dragging up and down; starts as the old tilt
3double lastX, lastY; // where the mouse was during the last frame
4glfwGetCursorPos(window, &lastX, &lastY);In the loop, after the keys, read the mouse:
1double mouseX, mouseY;
2glfwGetCursorPos(window, &mouseX, &mouseY); // from the top-left corner of the window
3if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
4 float perPixel = glm::radians(0.5f); // half a degree for every pixel dragged
5 yaw += (float)(mouseX - lastX) * perPixel; // drag right: turn right
6 pitch += (float)(mouseY - lastY) * perPixel; // drag down: tip the top toward you
7 pitch = glm::clamp(pitch, glm::radians(-90.0f), glm::radians(90.0f));
8}
9lastX = mouseX; // every frame, pressed or not
10lastY = mouseY;| Line | What it does |
|---|---|
glfwGetCursorPos | the mouse position, measured from the top-left corner of the window. y grows downward, unlike OpenGL's y. |
mouseX - lastX | how far the mouse moved since the last frame. lastX is updated every frame, pressed or not, so the first frame of a drag only sees that frame's small move, never a jump from where the last drag ended. |
perPixel | the sensitivity: a 180-pixel drag turns the cube 90°. There is no dt here: a mouse move is already a distance, not a speed. |
mouseY - lastY | positive when you drag down, because the mouse's y grows downward. A positive pitch around x tips the top face toward you, so dragging down shows the top. |
glm::clamp | keeps pitch between −90° and 90°. Past 90° the cube would be upside down, and dragging right would turn it left. |
Then the model matrix: pitch takes the place of the fixed tilt, and yaw joins the spin:
1glm::mat4 model(1.0f);
2model = glm::rotate(model, pitch, glm::vec3(1.0f, 0.0f, 0.0f)); // 3rd: tip toward you (mouse up and down)
3model = glm::rotate(model, yaw, glm::vec3(0.0f, 1.0f, 0.0f)); // 2nd: turn (mouse sideways)
4model = glm::rotate(model, angle, glm::vec3(0.0f, 1.0f, 0.0f)); // 1st: its own spin (Space pauses it)The order is the turntable of step 3, for the same reason: pitch is applied last, so a sideways drag always turns the cube around its own up axis, whatever its tilt. The spin and the yaw are both around y and come one after the other, so they simply add up: yaw is a turn you hold, angle a turn that keeps going.
One VAO is enough for many cubes, as the Try it of Drawing a Cube showed: draw it several times, with a different model matrix each time. Each cube gets its own place and its own spin, and the mouse turns them all together. The turn they share goes into a matrix called scene, built once per frame from yaw and pitch. First step the camera back from 3 to 4 units, so there is room for three:
1glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -4.0f)); // was −3
2
3glm::mat4 scene(1.0f); // the mouse turns everything
4scene = glm::rotate(scene, pitch, glm::vec3(1.0f, 0.0f, 0.0f));
5scene = glm::rotate(scene, yaw, glm::vec3(0.0f, 1.0f, 0.0f));Then the draw call becomes a loop. Replace the model lines, the model glUniformMatrix4fv and glDrawElements (view and projection are sent once, before it):
1glm::vec3 positions[] = {
2 glm::vec3(-1.6f, 0.0f, 0.0f),
3 glm::vec3( 0.0f, 0.0f, 0.0f),
4 glm::vec3( 1.6f, 0.0f, 0.0f),
5};
6float rates[] = { 0.5f, 1.0f, 2.0f }; // half, normal and double speed
7
8glBindVertexArray(vao);
9for (int i = 0; i < 3; ++i) {
10 glm::mat4 model = scene; // 4th: the mouse turn
11 model = glm::translate(model, positions[i]); // 3rd: into its place
12 model = glm::rotate(model, angle * rates[i], glm::vec3(0.5f, 1.0f, 0.0f)); // 2nd: its own tumble
13 model = glm::scale(model, glm::vec3(0.6f)); // 1st: a bit smaller
14 glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
15 glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);
16}
17
18// The moon: a small cube that circles the middle one
19glm::mat4 moon = scene; // 5th: the mouse turn
20moon = glm::translate(moon, positions[1]); // 4th: to the middle cube
21moon = glm::rotate(moon, angle * 1.5f, glm::vec3(0.0f, 1.0f, 0.0f)); // 3rd: swing around it
22moon = glm::translate(moon, glm::vec3(0.8f, 0.0f, 0.0f)); // 2nd: out to radius 0.8
23moon = glm::scale(moon, glm::vec3(0.2f)); // 1st: small
24glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(moon));
25glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);Read each matrix from the bottom up, the order the vertex meets them. A cube is made smaller, tumbles around the leaning axis of Drawing a Cube, is moved to its place, and only then turned with the rest of the scene. The tumble comes before the move, so each cube spins in place. The moon is the orbit of the last chapter, now in 3D: it is moved out first and turned after, around y, so it circles the middle cube instead of spinning.
Starting each matrix from scene, not from the identity, is what makes the mouse turn everything at once, around the middle of the scene. The angle is shared too, so Space pauses all four. And as the moon passes behind the middle cube, the depth test hides it, with no extra code.
The whole file after step 5. Compared with the end of Rotating a Triangle, the changes are marked NEW: the colour-cube shaders with uView, the cube's vertices and indices with the EBO, the depth test, the yaw and pitch state, the mouse, the perspective camera and the drawing loop. The X, Y and Z keys are gone. The two shader helpers are shortened because they did not change.
1#include <glad/glad.h> // MUST come before glfw3.h
2#include <GLFW/glfw3.h>
3#include <iostream>
4#include <glm/glm.hpp>
5#include <glm/gtc/matrix_transform.hpp>
6#include <glm/gtc/type_ptr.hpp>
7
8// ── Shaders ─────────────────────────────────────── NEW: colour from the position, uView
9const char* vertexShaderSource = R"(#version 460 core
10layout (location = 0) in vec3 aPos;
11out vec3 vColor;
12uniform mat4 uModel;
13uniform mat4 uView;
14uniform mat4 uProjection;
15void main() {
16 gl_Position = uProjection * uView * uModel * vec4(aPos, 1.0);
17 vColor = aPos + 0.5;
18})";
19
20const char* fragmentShaderSource = R"(#version 460 core
21in vec3 vColor;
22out vec4 FragColor;
23void main() {
24 FragColor = vec4(vColor, 1.0);
25})";
26
27// ── Shader helpers (unchanged since First Shaders) ──────────────────────────
28GLuint compileShader(GLenum type, const char* src) { /* ... */ }
29GLuint makeProgram(const char* vsSrc, const char* fsSrc) { /* ... */ }
30
31void framebufferSizeCallback(GLFWwindow*, int width, int height) {
32 glViewport(0, 0, width, height);
33}
34
35int main() {
36 // ── 1–3. Window, GLAD, viewport (unchanged) ─────────────────────────────
37 if (!glfwInit()) return 1;
38 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
39 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
40 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
41 GLFWwindow* window = glfwCreateWindow(1280, 720, "Spin", nullptr, nullptr);
42 if (!window) { glfwTerminate(); return 1; }
43 glfwMakeContextCurrent(window);
44 glfwSwapInterval(1);
45 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) return 1;
46 int fbw, fbh;
47 glfwGetFramebufferSize(window, &fbw, &fbh);
48 glViewport(0, 0, fbw, fbh);
49 glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
50
51 // ── 4. One-time setup ────────────────────────────────────────────────────
52 // 4a. Shaders → program (unchanged)
53 GLuint shaderProgram = makeProgram(vertexShaderSource, fragmentShaderSource);
54 if (shaderProgram == 0) { glfwTerminate(); return 1; }
55
56 // 4b. Vertex data → VAO + VBO + EBO >>> NEW: the colour cube
57 float vertices[] = {
58 // x y z
59 -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, // 0–3: front
60 -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, // 4–7: back
61 };
62 GLuint indices[] = {
63 0, 1, 2, 0, 2, 3, // front
64 7, 6, 5, 7, 5, 4, // back
65 3, 2, 6, 3, 6, 7, // right
66 4, 5, 1, 4, 1, 0, // left
67 4, 0, 3, 4, 3, 7, // top
68 1, 5, 6, 1, 6, 2, // bottom
69 };
70 GLuint vao, vbo, ebo;
71 glGenVertexArrays(1, &vao);
72 glGenBuffers(1, &vbo);
73 glGenBuffers(1, &ebo);
74
75 glBindVertexArray(vao);
76 glBindBuffer(GL_ARRAY_BUFFER, vbo);
77 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
78 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
79 glEnableVertexAttribArray(0);
80 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
81 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
82 glBindVertexArray(0);
83 // <<< end of NEW
84
85 // 4c. Matrix uniforms and depth
86 GLint modelLoc = glGetUniformLocation(shaderProgram, "uModel");
87 GLint viewLoc = glGetUniformLocation(shaderProgram, "uView"); // NEW
88 GLint projectionLoc = glGetUniformLocation(shaderProgram, "uProjection");
89 glEnable(GL_DEPTH_TEST); // NEW
90
91 // 4d. The spin and the mouse turn, kept between frames
92 float angle = 0.0f;
93 bool spinning = true;
94 bool spaceWasDown = false;
95 float lastFrame = (float)glfwGetTime();
96 float yaw = 0.0f; // NEW
97 float pitch = glm::radians(30.0f); // NEW
98 double lastX, lastY; // NEW
99 glfwGetCursorPos(window, &lastX, &lastY); // NEW
100
101 // ── 5. The render loop ───────────────────────────────────────────────────
102 while (!glfwWindowShouldClose(window)) {
103 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
104 glfwSetWindowShouldClose(window, true);
105
106 float time = (float)glfwGetTime();
107 float dt = time - lastFrame;
108 lastFrame = time;
109
110 bool spaceDown = glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS;
111 if (spaceDown && !spaceWasDown) spinning = !spinning;
112 spaceWasDown = spaceDown;
113
114 float speed = glm::radians(90.0f);
115 if (spinning) angle += speed * dt;
116 if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) angle += speed * dt;
117 if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) angle -= speed * dt;
118
119 // >>> NEW: drag to turn
120 double mouseX, mouseY;
121 glfwGetCursorPos(window, &mouseX, &mouseY);
122 if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
123 float perPixel = glm::radians(0.5f);
124 yaw += (float)(mouseX - lastX) * perPixel;
125 pitch += (float)(mouseY - lastY) * perPixel;
126 pitch = glm::clamp(pitch, glm::radians(-90.0f), glm::radians(90.0f));
127 }
128 lastX = mouseX;
129 lastY = mouseY;
130 // <<< end of NEW
131
132 glClearColor(0.06f, 0.07f, 0.10f, 1.0f);
133 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
134
135 // >>> NEW: a perspective camera, and the turn the whole scene shares
136 int width, height;
137 glfwGetFramebufferSize(window, &width, &height);
138 float aspect = height > 0 ? (float)width / (float)height : 1.0f;
139 glm::mat4 projection = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
140 glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -4.0f));
141 glm::mat4 scene(1.0f);
142 scene = glm::rotate(scene, pitch, glm::vec3(1.0f, 0.0f, 0.0f));
143 scene = glm::rotate(scene, yaw, glm::vec3(0.0f, 1.0f, 0.0f));
144
145 glUseProgram(shaderProgram);
146 glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
147 glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
148 glBindVertexArray(vao);
149
150 glm::vec3 positions[] = {
151 glm::vec3(-1.6f, 0.0f, 0.0f),
152 glm::vec3( 0.0f, 0.0f, 0.0f),
153 glm::vec3( 1.6f, 0.0f, 0.0f),
154 };
155 float rates[] = { 0.5f, 1.0f, 2.0f };
156 for (int i = 0; i < 3; ++i) {
157 glm::mat4 model = scene;
158 model = glm::translate(model, positions[i]);
159 model = glm::rotate(model, angle * rates[i], glm::vec3(0.5f, 1.0f, 0.0f));
160 model = glm::scale(model, glm::vec3(0.6f));
161 glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
162 glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);
163 }
164
165 glm::mat4 moon = scene;
166 moon = glm::translate(moon, positions[1]);
167 moon = glm::rotate(moon, angle * 1.5f, glm::vec3(0.0f, 1.0f, 0.0f));
168 moon = glm::translate(moon, glm::vec3(0.8f, 0.0f, 0.0f));
169 moon = glm::scale(moon, glm::vec3(0.2f));
170 glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(moon));
171 glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);
172 // <<< end of NEW
173
174 glfwSwapBuffers(window);
175 glfwPollEvents();
176 }
177
178 // ── 6. Clean up ──────────────────────────────────────────────────────────
179 glDeleteVertexArrays(1, &vao);
180 glDeleteBuffers(1, &vbo);
181 glDeleteBuffers(1, &ebo); // NEW
182 glDeleteProgram(shaderProgram);
183 glfwTerminate();
184 return 0;
185}| What you see | Likely cause |
|---|---|
| a square, not a cube | the cube turns around z, the axis that points at you, or does not turn at all. Press Y, or check the model matrix. |
| back faces show through the front ones | glEnable(GL_DEPTH_TEST) is missing, or glClear lacks GL_DEPTH_BUFFER_BIT. |
| the cube is huge, or nothing is drawn | the view matrix is missing or moves by +3: the camera sits inside the cube or in front of it, looking away. uView must be set and multiplied in the shader. |
| dragging turns it the wrong way | a sign is flipped. Dragging right should add to yaw, dragging down should add to pitch; the mouse's y grows downward. |
| it jumps when you start dragging | lastX and lastY are updated only while the button is held, so the first frame of a new drag measures from where the last one ended. Update them every frame. |
| the cubes swing around the middle instead of spinning in place | translate and rotate are swapped: each cube is moved first and turned after, so it orbits, which is the moon's recipe. Write translate before rotate. |
| Mistake | What happens, and the fix |
|---|---|
| Writing the rotations in the order they should happen | GLM's last call acts first. To spin and then tilt, write the tilt first. When a motion looks wrong, swapping two lines is the first thing to try. |
| Multiplying the mouse move by dt | a drag is a distance, so the turn should depend only on how far you dragged. With dt, the same drag turns less on a faster machine. |
| No limit on pitch | past 90° the cube is upside down and a sideways drag turns it the opposite way. Clamp pitch to ±90°. |
| Giving each cube its own copy of the mouse turn | starting each model from the identity and adding yaw and pitch after the translate turns every cube around its own centre, not the row around its middle. Start every model from scene. |
| Forgetting the EBO in the clean-up | nothing visible happens: the program ends anyway. But every glGen needs its glDelete, and later programs create buffers while they run. |
The scene matrix you just built is a camera in disguise: turning the whole world is the same as walking around it. Next, Camera & View Matrix makes that real, back in main.cpp: glm::lookAt, the mouse to look around and the keyboard to walk.
Key ideas
- Turning around one axis hides two faces: around y you never see the top and bottom, around x never the sides, and around z, the axis pointing at you, a cube looks like a flat square.
- Two rotations do not commute. GLM's last call acts first: tilt then spin (written) is a turntable, spin then tilt is a wobble.
- Yaw (around y) and pitch (around x) from a mouse drag turn an object like a 3D editor: pitch written first, so it acts last; clamp it to ±90°.
- A mouse drag is a distance: multiply it by an angle per pixel, never by dt.
- One VAO, many draws: each object gets its own model matrix. Starting each from a shared scene matrix turns them all together.
- Move then turn (in the order the vertex meets them) makes an object orbit; turn then move makes it spin in place.