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:
doublev[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:
intfaces[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:
intedges[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:
doublexz=x*cos(a) -z*sin(a); /* rotate Y */doublezz=x*sin(a) +z*cos(a);
doubleyz=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:
doublef=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 (inti=0; i<6; i++) {
avg_z[i] =0;
for (intj=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:
staticvoidfill_tri(Uint32*pixels, intx0, inty0,
intx1, inty1, intx2, inty2, Uint32color)
{
/* compute bounding box */for (eachpixelinboundingbox) {
doubledenom=1.0 / ((y1-y2)*(x0-x2) + (x2-x1)*(y0-y2));
doublew0= ((y1-y2)*(x-x2) + (x2-x1)*(y-y2)) *denom;
doublew1= ((y2-y0)*(x-x2) + (x0-x2)*(y-y2)) *denom;
doublew2=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:
staticvoiddraw_line(Uint32*pixels, intx0, inty0, intx1, inty1, Uint32color)
{
intdx=x1-x0, dy=y1-y0;
intsteps= (int)(fmax(abs(dx), abs(dy)));
for (inti=0; i <= steps; i++) {
intx=x0+dx*i / steps;
inty=y0+dy*i / steps;
if (inbounds) 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:
Uint32elapsed=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