A real-time 3‑D cube rendered with filled faces and white wireframe edges, tumbling under dual-axis rotation. Each face is a different colour. Rendered via SDL2 using the painter's algorithm for correct depth ordering.
Eight vertices, twelve edges, six faces — pure geometry, no GPU.
- 8-vertex cube geometry with dual-axis rotation (Y then X)
- Perspective projection with true 3‑D divide
- Painter's algorithm: faces sorted by average Z, drawn back to front
- Barycentric triangle rasterisation for solid face fill
- White wireframe edges overlaid on filled faces
- Six-face colour palette: red, blue, green, yellow, cyan, magenta
- True 24‑bit color rendering via SDL2 framebuffer
- 960 × 640 window
- Keyboard and Ctrl-C quit handling
- Written entirely in C
Every frame, the eight cube vertices are rotated around the Y axis, then the X axis at 0.7× speed to produce a tumbling motion. Each vertex is projected to 2‑D via perspective divide. The six faces are sorted by their average rotated Z (painter's algorithm) and drawn back-to-front as filled quads (split into two triangles). White edges are then drawn over the faces for a clean wireframe finish.
Eight vertices at ±1 on each axis, centred at the origin:
double v[8][3] = {
{-1,-1,-1}, { 1,-1,-1}, { 1, 1,-1}, {-1, 1,-1},
{-1,-1, 1}, { 1,-1, 1}, { 1, 1, 1}, {-1, 1, 1},
};Six faces, each defined by four vertex indices:
int faces[6][4] = {
{0,1,2,3}, /* back (z = -1) */
{4,5,6,7}, /* front (z = +1) */
{0,1,5,4}, /* bottom (y = -1) */
{2,3,7,6}, /* top (y = +1) */
{1,2,6,5}, /* right (x = +1) */
{0,3,7,4}, /* left (x = -1) */
};Twelve edges connecting the vertices:
int edges[12][2] = {
{0,1},{1,2},{2,3},{3,0},
{4,5},{5,6},{6,7},{7,4},
{0,4},{1,5},{2,6},{3,7},
};Each frame the angle a increments by 0.05 radians. Every vertex is rotated around Y, then X:
double xz = x * cos(a) - z * sin(a); /* rotate Y */
double zz = x * sin(a) + z * cos(a);
double yz = y * cos(a*0.7) - zz * sin(a*0.7); /* rotate X */
zz = y * sin(a*0.7) + zz * cos(a*0.7);The X-axis rotates at 0.7× the Y-axis speed, producing a non-repeating tumble.
A virtual camera at Z = 4.0 looks at the origin. The perspective divide maps 3‑D to 2‑D:
double f = 4.0 / (4.0 + zz); /* perspective factor */
p[i][0] = xz * f * FOV + WIN_W / 2;
p[i][1] = yz * f * FOV + WIN_H / 2;FOV = 180 scales the projected coordinates to fill the window. Near points (small zz) are magnified; far points are compressed.
Faces are sorted by their average rotated Z coordinate, far to near:
for (int i = 0; i < 6; i++) {
avg_z[i] = 0;
for (int j = 0; j < 4; j++)
avg_z[i] += rz[faces[i][j]];
avg_z[i] /= 4.0;
}
/* Simple bubble sort — 6 items, negligible cost */Faces are drawn in sorted order so nearer faces correctly occlude farther ones.
Each quad face is split into two triangles (v0,v1,v2) and (v0,v2,v3), then filled using barycentric coordinates:
static void fill_tri(Uint32 *pixels, int x0, int y0,
int x1, int y1, int x2, int y2, Uint32 color)
{
/* compute bounding box */
for (each pixel in bounding box) {
double denom = 1.0 / ((y1-y2)*(x0-x2) + (x2-x1)*(y0-y2));
double w0 = ((y1-y2)*(x-x2) + (x2-x1)*(y-y2)) * denom;
double w1 = ((y2-y0)*(x-x2) + (x0-x2)*(y-y2)) * denom;
double w2 = 1.0 - w0 - w1;
if (w0 >= -ε && w1 >= -ε && w2 >= -ε)
row[x] = color;
}
}Six faces, six colours:
| Face | RGB |
|---|---|
| Back | 200, 60, 60 |
| Front | 60, 60, 200 |
| Bottom | 60, 200, 60 |
| Top | 200, 200, 60 |
| Right | 60, 200, 200 |
| Left | 200, 60, 200 |
Wireframe edges are drawn in near-white (230, 230, 240) on top of the filled faces, at full per-pixel resolution.
Edges use a simple digital line walk:
static void draw_line(Uint32 *pixels, int x0, int y0, int x1, int y1, Uint32 color)
{
int dx = x1 - x0, dy = y1 - y0;
int steps = (int)(fmax(abs(dx), abs(dy)));
for (int i = 0; i <= steps; i++) {
int x = x0 + dx * i / steps;
int y = y0 + dy * i / steps;
if (in bounds) pixels[y * pitch + x] = color;
}
}The step count equals the longer axis span so no gaps appear.
A 40 fps cap keeps the tumble smooth:
Uint32 elapsed = SDL_GetTicks() - frame_start;
if (elapsed < 25) SDL_Delay(25 - elapsed);git clone <this-repo>
cd cube
make
Or manually:
gcc cube.c -o cube -lm $(sdl2-config --cflags --libs)
Dependencies: SDL2 and a C compiler (gcc/clang).
Install SDL2 via Homebrew:
brew install sdl2
Or apt:
sudo apt install libsdl2-dev
./cube
Controls:
- ESC or Q — quit
- Ctrl-C — quit (fallback)
Edit constants at the top of cube.c:
WIN_W,WIN_H— window resolutionFOV— projection scale factor- Rotation speeds (
a += 0.05, X-axis multiplier0.7) - Face colours
face_col[]— any RGB triple - Edge colour
col_edgeand backgroundcol_bg
- 3‑D vertex geometry and edge/face topology
- Dual-axis rotation via 2‑D rotation matrices
- Perspective projection with homogeneous divide
- Painter's algorithm for hidden-surface removal
- Barycentric triangle rasterisation
- Digital differential analyser (DDA) line drawing
- SDL2 surface/pixel-buffer graphics
- Real-time animation with frame-rate capping
SDL.h— window management and pixel bufferstdio.h— stderr diagnosticsstdlib.h—NULLstring.h—memsetmath.h—cos,sin,fmin,fmax,fabssignal.h—SIGINThandler