# 3D Spinning Cube – Rotational Matrices and Projection
### Deriving 3D rotational matrices and rendering a spinning cube using C++ and ASCII art in the terminal
<br>
by Chueh-an Kuo(郭爵安)
**Full Report on HackMD:**
https://hackmd.io/@EhY6joeNTVu4Z9GCmlZRxQ/rysEQMpyeg
**GitHub(full code):**
https://github.com/ChuehanKuo/3D-Spinning-Cube-Rotational-Matrices-and-Projection
**Full Report on GitHub Gist:**
https://gist.github.com/ChuehanKuo/4581c451246b161e5deb5f9e79be576c
<br>
***See the Mathematical Derivation and Concepts section for my full handwritten derivations of the 3D rotation matrices, combined matrix calculation, and the 3D-to-2D projection.***
<br>
## Table of Contents
### Project Overview
#### 1. Abstract
#### 2. Motivation
#### 3. Objective
### Mathematical Derivation and Concepts
#### 1. From 2D Rotation to 3D Transformations
#### 2. 3D Rotation Matrices for X, Y, Z Axes
#### 3. Combining 3D Rotations
#### 4. Perspective Projection and Depth Mapping
### Code Structure
#### 1. Header Includes and Class Setup
#### 2. Constructor – Setting Up the Renderer
#### 3. Main Loop – Running the Cube Animation
#### 4. Private Variables and Terminal Setup
#### 5. Math Functions – 3D Rotation Formulas
#### 6. Rendering the Cube Faces – renderCube()
#### 7. Plotting a Single Point – drawPoint()
#### 8. Rotating the Cube – rotate()
#### 9. Displaying the Frame – display()
#### 10. Main Function – Running the Renderer
### Final Result
### Conclusion: Lessons & Goals:
#### 1. Reflection and Learning
#### 2. Future Plans
### Reference
<br>
## Abstract
**In this project, a 3D spinning cube is rendered in the terminal using C++ and ASCII art.** The cube is dynamically rotated in three dimensions using 3D rotation matrices for the X, Y, and Z axes. **The project involves deriving these matrices and applying perspective projection to map 3D coordinates onto a 2D terminal screen.** The cube’s rotation is continuously updated, giving it a smooth, dynamic spin. **This project combines mathematical concepts with programming to explore how linear algebra and graphics rendering can be applied even in a text-based environment.**
<br>
## Motivation
**My interest in creating a 3D spinning cube began when I came across a YouTube video showcasing a smooth, rotating cube in 3D space.** I was immediately intrigued by how such a simple visual could convey so much depth and movement. **Having already learned about 2D rotation matrices in school, I was curious about how the same principles could be applied to 3D rotation.** The idea of extending what I knew to a more complex 3D environment seemed like a perfect challenge, especially from a mathematical standpoint. The author of the YouTube wrote the code in C and also made an explantion video on the concepts. **Through his tutorial, I understood how the spinning cube actually works and implemented it myself in C++.**
**Around the same time, I was learning C++, and I saw this as the perfect opportunity to combine my programming skills with my growing understanding of 3D geometry.** I wanted to deeply explore the mathematics behind 3D rotation matrices, as I had already learned about 2D rotations and their application. **Deriving the 3D rotation matrices—for the X, Y, and Z axes—became the core challenge for me.** I was eager to understand how the rotation transformations work and how they could be applied to rotate a cube in space.
What excited me the most was applying these mathematical concepts to a real project, as it wasn’t just about writing code—it was about understanding the math and how it drives the 3D transformations. **Through the process, I not only got to implement the derivation of rotation matrices from scratch, but also to experiment with perspective projection, showing how 3D points are mapped to 2D space.** **This project allowed me to bridge theoretical knowledge and practical coding, and gave me the chance to see math in action.**
<br>
## Objective
**The primary goal of this project was to render a 3D spinning cube in the terminal using C++ and ASCII art.** **A significant part of the project involved understanding and applying 3D rotation matrices for the X, Y, and Z axes.** While the focus was on applying these matrices to achieve the cube’s rotation, a crucial step was deriving the mathematical formulas for each rotation. This process allowed me to gain a deeper understanding of how 3D transformations work and how they can be used to manipulate objects in space. **After deriving the rotation matrices, I applied them to the cube’s vertices in real-time, ensuring the cube would spin smoothly.** **Additionally, I implemented perspective projection, which mapped the rotated 3D points onto a 2D screen, using the Z-buffer to simulate depth.** By combining math and programming, I was able to visualize how mathematical concepts like rotation and projection come to life in a text-based environment.
<br>
## Mathematical Derivation and Concepts
### 1. From 2D Rotation to 3D Transformations
Before diving into 3D rotations, I revisited something familiar: 2D rotation.
In polar coordinates, a point’s position depends on its radius and angle, which can be converted to Cartesian coordinates like this:

<br>
To rotate this point by an angle 𝛼, we use the 2D rotation matrix:

This was my starting point, as I had learned it in school.
**The following Images shows the process in which I derive the three individual 3D matrices.**
<br>
**Handwritten notes by me**

***-> This image shows the basic concept of 2D rotation, which will later be expanded into 3D rotation.***
<br>
**Handwritten notes by me**

***-> This image derives the matrix(formula) for 2D anti-clockwise rotation.***
<br>
### 2. 3D Rotation Matrices for X, Y, Z Axes
**Once I felt comfortable with 2D rotations, I extended the idea into 3D space.**
Instead of just rotating around a flat plane, in 3D, we rotate points around each axis (X, Y, Z). Each axis has its own rotation matrix:

Each of these tells how a point moves when rotated around a specific axis.
**The following Images shows the process in which I derive the three individual 3D matrices.**
<br>
**Handwritten notes by me**

***-> This image derives the 3D rotation matrix for when the rotation axes is the y axes.***
<br>
**Handwritten notes by me**

***-> This image derives the 3D rotation matrix for when the rotation axes is the z axes.***
<br>
**Handwritten notes by me**

***This image derives the 3D rotation matrix for when the rotation axes is the x axes.***
<br>
### 3. Combining 3D Rotations
To get the cube to spin around all three axes, I needed to combine the three rotation matrices.
This means **multiplying them together in a specific order** (Z, then Y, then X):

This combined matrix makes sure the cube spins smoothly in 3D space, with all rotations applied together.
Then we multiply this combined rotation matrix with a variable coordinate to get the final formula.
**The following image shows the process in which I multiply the individual matrices, that combined matrix is then multipled by a varible coordinate (i,j,k) to get the final 3D rotation formula used in this project.**
<br>
**Handwritten notes by me**

***-> This image multiplies the individule matrices in a specific order, then multiply the combined matrix with the variable point coordinate (i,j,k) to result in the final formula for 3D rotation.***
<br>
### 4. Perspective Projection and Depth Mapping
Once the cube’s points are rotated, **the next step is to project them onto the 2D screen (the terminal).**
**This uses perspective projection**, where points that are farther away appear smaller—like how things look in real life.

This makes sure that depth (Z) affects how large or small a point looks on the screen—because **the size of an object is proportional to the inverse of its distance from the viewer.** The farther away a point is, the smaller it appears; the closer it is, the larger it looks. **This is what creates the perspective effect that makes the cube feel three-dimensional.**
The following image visualizes the concept of the spinning cube projection in the terminal.
<br>
**Handwritten notes by me**

***-> This image shows different perspective of the 3D to 2D projection.***
<br>
## Code Structure
When I started this project, I wasn’t just focused on getting a cube to spin — **I also wanted to get better at writing clean, well-structured code.** Instead of just throwing together a bunch of functions, **I decided to organize everything using object-oriented programming.** I created a class called CubeRenderer to keep all the logic in one place — from handling the math for 3D rotation to drawing everything in the terminal with ASCII characters.
**Using a class made it easier to keep things modular and easy to manage.** Rotation logic, projection math, and screen rendering are all separated into their own parts inside the class, which made the code a lot more readable. **It also helped me think more clearly about how to design programs in a way that scales.**
**The overall loop is simple: clear the screen, rotate the cube, draw it, and repeat.** But getting all of that to work smoothly — especially the math and the visual rendering — definitely took some trial and error.
**In the sections below, I’ll walk through the code exactly in the order I wrote it. After each part, I’ll also include some notes about what I learned while writing it, and what kind of problems I ran into along the way.**
<br>
### 1. Header Includes and Class Setup
```cpp
#include <cmath>
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
```
* cmath: for trigonometric functions like sin() and cos().
* iostream: for printing the cube frame to the terminal.
* vector: used for the frame buffer and z-buffer.
* string: to build and flush full frames efficiently.
* thread and chrono: for adding delays between frames.
<br>
After importing the headers, I define the CubeRenderer class:
```cpp
class CubeRenderer {
public:
```
**The idea was to keep all the rendering logic encapsulated in one place, so I wouldn't have to deal with messy global variables or scattered code. By using object-oriented programming (OOP), I could define variables like the cube size, screen dimensions, and rotation angles, and have full control over how the cube behaves across frames.**
<br>
#### Reflections:
**When I first wrote this part, I was just starting to get comfortable with classes in C++. Writing everything inside a single class taught me a lot about encapsulation and the importance of keeping things organized. It also made debugging easier since I could manage state more clearly and didn't need to worry about passing variables between scattered functions.**
<br>
### 2. Constructor – Setting Up the Renderer
```cpp
CubeRenderer(int w = 80, int h = 24) //terminal dimension, default: 80*24
: width(w), height(h),
zBuffer(width * height, 0), //depth buffer, zero is a point at infinity
buffer(width * height, ' '), //character buffer, initialized with spaces
A(0), B(0), C(0), //rotation angles for different axes, beginning value
cubeWidth(20),
distanceFromCam(100),//push cube toward positive z
zoomlevel(30) {} //projection scaling
```
* **width, height**: terminal screen dimensions (using default dimensions which is 80x24).
* **zBuffer**: stores depth (1/z) values for each character cell to handle overlapping characters.
* **buffer**: the character array that holds what will be printed on the screen. There is blank spaces (for background screen), and ASCII characters (for cube faces).
* **A, B, C**: rotation angles for the X, Y, and Z axes, all initialized to 0.
* **cubeWidth**: controls the cube’s size.
* **distanceFromCam**: pushes the cube away from the origin along the z-axis so it doesn’t sit inside the camera. This part makes sure z values are always positive (used in later code to make sure the whole cube sits in front of the camera).
* **zoomlevel**: controls how “zoomed in” the projection is on screen. Directly affects size of the cube, a smaller zoomlevel would result in a bigger projection.
Using an initializer list made the code cleaner and ensured that zBuffer and buffer would be created with the correct sizes immediately.
<br>
#### Reflections:
**When I first set up the constructor, I didn’t really understand what the zBuffer was doing or why it was necessary. I initialized it with zeros just because I saw other examples do it, but I didn’t fully grasp how it affected the rendering. Later, as I worked on the drawing logic, I noticed that parts of the cube would overlap incorrectly or flicker. After looking into how 3D rendering works, I learned that the zBuffer is used to keep track of the depth of each point on screen—so only the closest surface is drawn at any pixel.**
**Understanding this concept helped me realize why initializing the zBuffer to zero was essential: it represented the farthest depth (since I was using 1/z as the depth measure), and allowed closer points to overwrite farther ones. Once I understood that, the cube rendering made a lot more sense and I was able to debug the visual issues much more easily.**
<br>
### 3. Main Loop – Running the Cube Animation
After setting up the initial state, the program enters the main loop inside the run() function. **This is where everything comes together: the cube is rendered, rotated, and updated continuously to create the spinning effect.**
```cpp
void run() {
//setup the terminal
clearScreen(); //clear terminal
hideCursor(); //hide terminal cursor
while (true) {
//reset buffer, ensuring each frame starts clean
std::fill(buffer.begin(), buffer.end(), ' ');
std::fill(zBuffer.begin(), zBuffer.end(), 0);
// Render cube
renderCube();
// Display and rotate
display();
rotate();
// Wait
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
}
/*
~CubeRenderer() { //destructor, run when program ends
showCursor();
}
*/
```
Before the loop begins, I clear the screen and hide the cursor to keep the terminal output clean.
These are the main usage of this section of code:
* **Clearing the buffers**: each frame starts fresh.
* **Rendering the cube**: calculates the new 3D positions.
* **Display**: draws the updated cube to the terminal.
* **Rotate**: changes the angles so the cube spins.
* **Sleep**: waits briefly before rendering the next frame (to control speed).
This is an always true while loop, so the cube will continue to spin without stopping.
<br>
#### Reflections:
**Writing the main loop was where everything finally came together, but it wasn’t as straightforward as I expected. At one point, the cube was leaving trails and smudges on the screen, and I couldn’t figure out why. After reading up on depth buffering, I realized I needed to reset the zBuffer each frame. Once I added that step, the cube started rendering correctly without breaking.**
**While setting up the loop, I also thought about whether it was necessary to control things like the cursor or error handling. I decided to include hideCursor() because it made the animation much cleaner, removing any distractions from the spinning cube. On the other hand, I chose not to implement showCursor() at the end, since I usually just close the terminal after running the program. For error handling, I originally considered using a try-catch block, but after checking how my loop worked, I realized nothing inside would realistically throw an exception, so I left it out to keep the code simple.**
**Another part I spent time adjusting was the rotation speed. I experimented with different values for A, B, and C, and noticed that slightly varying the speeds made the spin feel a lot more natural. Instead of rotating symmetrically around all axes, the cube had a more dynamic motion. Finding that balance took some trial and error, but it was one of the most satisfying parts of the project.**
<br>
### 4. Private Variables and Terminal Setup
After setting up the main loop, the next part of the code defines all the variables and helper functions that keep the rendering process running smoothly.
```cpp
private:
// State variables
int width, height;
std::vector<float> zBuffer; //hold zbuffer values
std::vector<char> buffer; //hold character to draw
float A, B, C;
float cubeWidth;
int distanceFromCam;
float zoomlevel;
```
After the main loop, I defined a few private variables to hold the cube’s state, like its size, rotation angles, screen dimensions, and depth information. I had already thought through what I needed when designing the class structure, so this section was mostly about setting up the variables I would rely on later for rendering and projection.
**Everything is kept as member variables inside CubeRenderer, which made it easier to update things like the cube's rotation or screen projection without passing a bunch of parameters around.**
<br>
To clear the screen before drawing each new frame, I wrote a simple clearScreen() function:
```cpp
/*
This was the original version, since im using a macbook, this was used.
void clearScreen() {
system("clear");
}
Later I found out that different operating systems required different commands, I implemented cls for windows
*/
// Terminal control
void clearScreen() { //refresh screen for each frame
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
```
At first, I thought clearing the screen would be straightforward, but after looking into it more carefully, I found that different operating systems actually use different commands — cls on Windows, and clear on macOS and Linux. **Since I’m working on a MacBook, I made sure to handle both cases so that the program would stay portable no matter where it’s run.**
<br>
```cpp
void hideCursor() {
std::cout << "\x1b[?25l"; //ANSI escape code
}
/*
void showCursor() {
std::cout << "\x1b[?25h";
}
*/
//skipped cursor reshowing since I close the terminal after running
```
This uses an ANSI escape sequence to hide the terminal cursor, so the blinking cursor doesn't distract from the spinning cube.
**I also had a showCursor() function at first, but since I close the terminal after running the program anyway, I commented it out to keep the code simpler.**
<br>
#### Reflections:
**Setting up the private variables felt pretty straightforward at first, but getting the terminal behavior right took a lot more digging than I expected. Originally, I just used system("clear") to wipe the screen after each frame, since I was developing on a MacBook. Later, when I thought about making the code more portable, I found out that Windows uses a completely different command (cls). That’s when I learned how to write a cross-platform solution using #ifdef _WIN32, so that the program would work no matter which operating system it's run on.**
**I also wasn’t sure at first whether hiding the cursor really mattered. After looking into how terminal rendering works, I found out about ANSI escape codes — special sequences you can send to the terminal to control things like cursor visibility. I added hideCursor() using an ANSI escape sequence, and it immediately made the animation look much cleaner and less distracting. Small details like screen clearing and cursor control ended up making a huge difference in making the final output feel smooth and professional.**
<br>
### 5. Math Functions – 3D Rotation Formulas
After setting up the terminal controls and buffers, the next part of the code deals with the math that makes the cube spin: **calculating how each 3D point moves when rotated around the X, Y, and Z axes.**
I implemented three functions: calculateX(), calculateY(), and calculateZ(), **based on the combined 3D rotation matrix I derived myself.**
```cpp
//math functions, combined 3 matrices for this final result
float calculateX(float i, float j, float k) {
return i * cos(C) * cos(B) + j * cos(C) * sin(B) * sin(A) - j * sin(C) * cos(A)
+ k * cos(C) * sin(B) * cos(A) + k * sin(C) * sin(A);
}
float calculateY(float i, float j, float k) {
return i * sin(C) * cos(B) + j * sin(C) * sin(B) * sin(A) + j * cos(C) * cos(A)
+ k * sin(C) * sin(B) * cos(A) - k * cos(C) * sin(A);
}
float calculateZ(float i, float j, float k) {
return i * -(sin(B)) + j * cos(B) * sin(A) + k * cos(B) * cos(A);
}
```
**Each function takes the original (i, j, k) position of a point on the cube and applies a rotation based on the current angles A, B, and C.**
These formulas are the final result of multiplying the three individual 3D rotation matrices together and then applying them to a point, just like I derived in the mathematical section earlier.
* calculateX() gives the rotated x-coordinate.
* calculateY() gives the rotated y-coordinate.
* calculateZ() gives the rotated z-coordinate.
<br>
#### Reflections:
**This was one of the hardest parts of the project, because it forced me to really understand how 3D rotation matrices work. I didn’t just want to copy the formulas — I wanted to derive them myself. I spent a lot of time writing out the individual rotation matrices for X, Y, and Z rotations, then multiplying them step-by-step by hand to find the final combined formula.**
**At first, I made small mistakes, like mixing up the order of multiplication, or accidentally switching signs on the sine terms. I had to go back several times, recheck each derivation, and make sure the matrices matched how rotations work in 3D space.**
**Understanding that 3D rotations don't simply "add up" like 2D ones — and that the order of applying X, Y, and Z rotations matters — was a huge realization for me. It made me appreciate how subtle linear algebra can be, even for something that looks simple like rotating a cube.**
**In the end, being able to write out the final formulas myself and implement them directly into code felt like a big achievement. It connected the math side and the programming side in a really satisfying way.**
<br>
### 6. Rendering the Cube Faces – renderCube()
Once the math was set up, the next step was to actually draw the cube’s six faces. I wrote a function called renderCube() to handle this.
```cpp
//rendering function
void renderCube() {
float increment = 0.8; //density of each side
char chars[] = {'@', '#', '%', '.', '=', '^'}; //characters for each side
```
* Increment controls the density of points on each face. A smaller increment would draw more points and make the cube look "smoother," but also slower to render. The number 0.8 was the result of trial and error, it wasn't a magic number.
* chars array holds different ASCII characters for each face of the cube, to make the cube more realistic by giving it different shades for each sides.
<br>
Then, I looped through all the x and y coordinates across each face:
```cpp
for (float x = -cubeWidth; x < cubeWidth; x += increment) {
for (float y = -cubeWidth; y < cubeWidth; y += increment) {
//front side
drawPoint(x, y, -cubeWidth, chars[0]);
//right side
drawPoint(cubeWidth, y, x, chars[1]);
//left side
drawPoint(-cubeWidth, y, -x, chars[2]);
//back side
drawPoint(-x, y, cubeWidth, chars[3]);
//bottom side
drawPoint(x, -cubeWidth, -y, chars[4]);
//top side
drawPoint(x, cubeWidth, y, chars[5]);
}
}
}
```
For each face, I fixed one of the coordinates (either x, y, or z) and varied the other two across a grid. Then I called drawPoint() to draw each point onto the screen.
<br>
#### Reflections:
**When I first thought about drawing a 3D cube, I imagined just drawing the corners and edges — but that would have made the cube look empty when rotating. After thinking more about it, I realized I needed to actually fill out each face with many points to make it feel solid. But this came at a cost of difficulty, due to this I had to understand Z-Buffering to make the cube more realistic by hiding the the points that are overlapped.**
**One thing I struggled with was figuring out which coordinates to fix for each face. For example, on the front face, z stays constant, but on the right face, x is constant. It took a few sketches and thinking through the cube's structure in 3D space before it finally clicked.**
**I also experimented with the increment value. Smaller increments made the cube look better but slowed down the rendering. I had to balance visual quality with performance to find a good middle ground (I settled on 0.8).**
<br>
### 7. Plotting a Single Point – drawPoint()
After setting up how the cube’s faces are drawn, the next step was figuring out how to correctly plot each individual point.
**The drawPoint() function handles rotating a 3D point, projecting it into 2D, and deciding whether it should appear on the screen.**
```cpp
void drawPoint(float x, float y, float z, char character) {
//calculate 3D coordinates
float xp = calculateX(x, y, z);
float yp = calculateY(x, y, z);
float zp = calculateZ(x, y, z) + distanceFromCam;
if (zp <= 0) return; //behind the camera check, if behind, then skip drawing this point
//project to 2D
float oneoverz = 1 / zp; //one over z, used for zbuffering
int screenx = static_cast<int>(width / 2 + zoomlevel * oneoverz * xp * 2); // width/2 for centering the projection
int screeny = static_cast<int>(height / 2 + zoomlevel * oneoverz * yp); // height/2 for centering the projection
// width/2 and height/2 are necessary because terminal starts at left-top corner
//check bounds, make sure projection stay within the terminal screen
if (screenx < 0 || screenx >= width || screeny < 0 || screeny >= height) return;
//converts 2D screen coords to 1D index for buffers, this is the generic formula: index=y×width+x
int index = screeny * width + screenx;
//Z-buffer check
if (oneoverz > zBuffer[index]) { //zBuffer[idx] holds the closest depth
zBuffer[index] = oneoverz;
/*
if a point goes closer(over) than current stored point, then update to that point
meaning only showing closest point
*/
buffer[index] = character; //draw the character of that point
}
}
```
* First, I used my previously derived rotation functions (calculateX, calculateY, calculateZ) to rotate the point.
* I then pushed the point away from the camera by adding distanceFromCam to the z-coordinate. Without this step, I realized the cube would sit right on the origin and display weirdly onto the terminal screen.
* Next, I applied perspective projection using 1/zp, so that farther points appear smaller on the screen.
* After calculating the projected screen coordinates (screenx, screeny), I added boundary checks to make sure no invalid points got written outside the terminal window.
* Finally, I used a simple z-buffering system: only update a pixel if this point is closer to the camera than what was previously drawn.
<br>
#### Reflections:
**One major problem I ran into was that at first, the cube looked distorted or even disappeared. After digging deeper into 3D rendering concepts, I realized that if the points had a z-value ≤ 0 (meaning they were behind the camera), the perspective math would completely break. Adding distanceFromCam to push the cube forward into positive z-space solved this issue and made the cube visible and projectable.**
**Understanding why I had to project using 1/z was another important realization. Without it, the farther sides of the cube would look just as big as the closer sides, and the 3D effect would be totally lost. I remembered from Natural Science class that the size of an object is proportional to the inverse of its distance. Applying this concept here showed me how real-world perspective actually works — and it was satisfying to see that simple idea reflected in real math and code. I also hand-drawed a visualization of how the perspective actually works, giving me further evidence of the things taught at school.**
**Another thing I had to figure out was how to flatten the 2D screen into a 1D buffer for storing the points efficiently. The formula index = y * width + x seemed abstract at first, but once I implemented it and saw it working, it made complete sense. It was cool to realize that behind what looks like a "2D" image, it’s really just memory management under the hood.**
**This function tied together everything — 3D rotation, perspective scaling, depth checking, and memory mapping — and it really made the cube feel alive when it all clicked into place.**
<br>
### 8. Rotating the Cube – rotate()
After rendering each frame, I needed to update the rotation angles so that the cube would spin over time.
This is done by a simple function called rotate():
```cpp
void rotate() { //increase rotation angles, this is where the spinning happens
A += 0.05;
B += 0.05;
C += 0.01;
}
};
```
<br>
#### Reflections:
**Here, A, B, and C are the rotation angles around the X, Y, and Z axes. I made A and B rotate at the same speed, but gave C a slower rotation to make the spinning feel more dynamic and natural.**
**At first, I gave all the angles (A, B, and C) the same speed because it seemed simple. But after running the program, the cube’s movement felt stiff and repetitive — like it was locked into a mechanical rotation. It didn’t have that natural, floating feeling I saw in other demos.**
**After experimenting, I realized that slightly offsetting the rotation speeds made a huge difference. Slowing down the Z-axis (C) while keeping X and Y faster gave the cube a much more dynamic and natural motion.**
**It also taught me how sensitive 3D animations are to small changes. Even tiny differences in speed can completely change how the motion feels.
This trial-and-error process of tuning the rotation speeds ended up being one of the most fun parts of the project.**
<br>
### 9. Displaying the Frame – display()
```cpp
void display() {
std::string frame;
//reserve 80*24+24 memory, +height is for the newline character '\n'
frame.reserve(width * height + height);
//move cursor to top-left, prepare terminal to overwrite last frame
frame = "\x1b[H";
for (int y = 0; y < height; y++) {
//&buffer[y * width] gets address of first character
//append width number of characters for each row
frame.append(&buffer[y * width], width);
frame.push_back('\n');
}
std::cout << frame << std::flush; //flush so prints the whole screen at once
}
```
* Reserved enough memory for all characters and newlines in advance to prevent slowdowns.
* Used an ANSI escape code to move the cursor back to the top-left corner, so the next frame would overwrite the previous one instead of printing endlessly downward.
* Appended each row from the buffer into a big frame string, line by line.
* Finally flushed the whole frame at once with std::cout << frame << std::flush, ensuring smooth updates.
<br>
#### Reflections:
**When I started building the frame for the cube, I initially thought I could just allocate width * height characters to hold all the points. But when I ran the program, weird glitches started appearing — sometimes parts of the cube disappeared, or the formatting would break randomly.**
**After looking closer, I realized that each row needed an extra character for the newline ('\n'), which I had completely forgotten to account for. Without reserving space for the newlines, the frame would overflow into unexpected areas of memory, causing unpredictable behavior.**
**Once I understood this, I updated the code to reserve width * height + height characters instead of just width * height. That fixed the bugs immediately.
It was a small mistake, but it taught me a huge lesson: when dealing with low-level memory operations, even small details like a missing newline can cause big problems.**
**It also gave me a deeper appreciation for why careful memory management matters — even in a simple project like this. It’s not just about getting the math right; it’s about making sure everything is correctly organized and sized behind the scenes too.**
<br>
### 10. Main Function – Running the Renderer
Finally, the main() function sets everything in motion:
```cpp
int main() {
CubeRenderer cube;
cube.run();
return 0;
}
```
* I simply created an instance of the CubeRenderer class called cube.
* Then I called cube.run(), which starts the main rendering loop that continuously draws and rotates the spinning cube.
* After the loop exits (which doesn’t happen unless the program is manually stopped), the program returns 0, signaling successful execution.
* The main() function is minimal on purpose — all the heavy lifting is done inside the class. This structure keeps the overall code clean and lets the object manage itself.
<br>
#### Reflections:
**At first, I thought about putting everything — setting up buffers, clearing the screen, rotating the cube — directly into main().
But as I kept building, I realized it would get messy really fast. That’s when I decided to organize everything into a class.**
**Creating a CubeRenderer class made it so much easier to structure the project cleanly. All the math, rendering, and updating are neatly contained inside one object. It was one of my first times really applying Object-Oriented Programming (OOP) in a project, and it made me appreciate how useful OOP can be, even for something as small as a terminal animation.**
**Another thing I learned was how important it is to keep main() simple — it should just describe the "big picture" of the program: create the object, start running it, and exit cleanly.
It’s a style that makes the code much easier to maintain and understand, especially if the project grows bigger later on.**
<br>
## Final Results
By the end of this project, I successfully created a 3D spinning cube rendered entirely in the terminal using C++ and ASCII art.
The cube smoothly rotates along the X, Y, and Z axes, applying the correct 3D rotational matrices derived from scratch. Perspective projection is implemented properly, giving the cube a realistic sense of depth as it spins.
The Z-buffering system correctly handles overlapping faces, ensuring that only the nearest surfaces are displayed.
The animation runs smoothly in a simple text-based environment, demonstrating how mathematical concepts like rotation matrices and projection can be brought to life.
<br>
**Below is the final result visualized as images that show different frames:**



<br>
**Below is the final result visualized as a video that shows the whole cube spinning animation:**
{%youtube 7g2LB-hgA6Q %}
<br>
## Reflection and Learning
This project taught me far more than I expected. **I didn’t just implement 3D rotation—I derived the math behind it by hand, starting from the 2D rotation matrix I learned in school.** Working through the polar coordinate system and carefully translating it into 3D rotational matrices for the X, Y, and Z axes gave me a much deeper appreciation for how math powers visual transformations.
**Understanding perspective projection was another turning point. I remember initially wondering why we needed to divide by z, until I recalled a concept from my natural science class: size is inversely proportional to distance.** Once I applied that to 3D rendering, it all clicked. That realization made the cube feel real on screen, even if it was just ASCII.
Debugging was also a big part of my learning. For example, **I initially didn’t understand what the Z-buffer was doing—until I noticed my cube was smearing weirdly on itself. I read more about depth testing and finally saw that I needed to clear and reuse the zBuffer correctly.** Another time, I forgot to allocate space for the newline characters when rendering the final frame and ran into strange memory bugs. **Solving these issues taught me how even small details matter when working close to the hardware.**
**More than anything, this project gave me a hands-on way to connect math, memory handling, and rendering logic—all wrapped in an object-oriented structure I designed from scratch.** **I now see how concepts like matrix multiplication, memory allocation, and coordinate transformations all come together to form something visual and intuitive.**
<br>
## Future Plans
Now that I’ve built a functioning 3D cube renderer in the terminal, **I want to keep exploring the connection between math and computer graphics. One area I’m especially interested in is real-time interaction—allowing the user to control the cube’s rotation using keyboard input.** This would help me learn more about event handling and how input can be mapped to transformations in space.
**I also want to go deeper into the math.** While rotation matrices are a great starting point, I’ve come across topics like quaternions, which are supposed to handle 3D rotation in a more stable way. **I’d like to study how they work and maybe try implementing a quaternion-based version of this project as a comparison.**
**Another future step would be to refactor the code for modularity and scalability.** Right now, everything is inside one class, but I’ve started thinking about how to split the math, rendering, and terminal logic into separate components—something that will be important for larger projects later on.
**Above all, I hope to continue using small projects like this to apply math in hands-on ways.** Being able to see a concept like a rotation matrix in action made it stick with me much more than just reading about it in a textbook.
<br>
## Reference
1. Project Inspiration – Source:
https://youtu.be/p09i_hoFdd0?si=UUbt4dOeF96z7FD-
https://youtu.be/0E0UBphVRhY?si=YUnm6ZM5O3TeYS87
https://github.com/servetgulnaroglu/cube.c
2. 2D Rotation Matrix Image – Source:
https://graphicmaths.com/pure/matrices/matrix-2d-transformations/
3. 3D Rotation Matrix Image – Source:
https://en.wikipedia.org/wiki/Rotation_matrix