Last active
March 9, 2026 05:44
-
-
Save jonas1ara/a768aebb37fe7131636438423366cdd9 to your computer and use it in GitHub Desktop.
Cube in F#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| open System | |
| let mutable A, B, C = 0.0, 0.0, 0.0 | |
| let mutable cubeWidth = 20.0 | |
| let width, height = 160, 44 | |
| let zBuffer = Array.create (width * height) 0.0 | |
| let buffer = Array.create (width * height) ' ' | |
| let backgroundASCIICode = '.' | |
| let distanceFromCam = 100.0 | |
| let mutable horizontalOffset = 0.0 | |
| let K1 = 40.0 | |
| let incrementSpeed = 0.6 | |
| let mutable x, y, z = 0.0, 0.0, 0.0 | |
| let mutable ooz = 0.0 | |
| let mutable xp, yp, idx = 0, 0, 0 | |
| let calculateX i j k = | |
| j * (sin A) * (sin B) * (cos C) - k * (cos A) * (sin B) * (cos C) + | |
| j * (cos A) * (sin C) + k * (sin A) * (sin C) + i * (cos B) * (cos C) | |
| let calculateY i j k = | |
| j * (cos A) * (cos C) + k * (sin A) * (cos C) - | |
| j * (sin A) * (sin B) * (sin C) + k * (cos A) * (sin B) * (sin C) - | |
| i * (cos B) * (sin C) | |
| let calculateZ i j k = | |
| k * (cos A) * (cos B) - j * (sin A) * (cos B) + i * (sin B) | |
| let calculateForSurface cubeX cubeY cubeZ ch = | |
| x <- calculateX cubeX cubeY cubeZ | |
| y <- calculateY cubeX cubeY cubeZ | |
| z <- calculateZ cubeX cubeY cubeZ + distanceFromCam | |
| ooz <- 1.0 / z | |
| xp <- int (float width / 2.0 + horizontalOffset + K1 * ooz * x * 2.0) | |
| yp <- int (float height / 2.0 + K1 * ooz * y) | |
| idx <- xp + yp * width | |
| if idx >= 0 && idx < width * height then | |
| if ooz > zBuffer.[idx] then | |
| zBuffer.[idx] <- ooz | |
| buffer.[idx] <- ch | |
| let drawFrame () = | |
| buffer.[0 .. width * height - 1] <- Array.init (width * height) (fun _ -> backgroundASCIICode) | |
| Array.fill zBuffer 0 (width * height) 0.0 | |
| cubeWidth <- 20.0 | |
| horizontalOffset <- -2.0 * cubeWidth | |
| for cubeX in -cubeWidth .. incrementSpeed .. cubeWidth do | |
| for cubeY in -cubeWidth .. incrementSpeed .. cubeWidth do | |
| calculateForSurface cubeX cubeY -cubeWidth '@' | |
| calculateForSurface cubeWidth cubeY cubeX '$' | |
| calculateForSurface -cubeWidth cubeY -cubeX '~' | |
| calculateForSurface -cubeX cubeY cubeWidth '#' | |
| calculateForSurface cubeX -cubeWidth -cubeY ';' | |
| calculateForSurface cubeX cubeWidth cubeY '+' | |
| cubeWidth <- 10.0 | |
| horizontalOffset <- 1.0 * cubeWidth | |
| for cubeX in -cubeWidth .. incrementSpeed .. cubeWidth do | |
| for cubeY in -cubeWidth .. incrementSpeed .. cubeWidth do | |
| calculateForSurface cubeX cubeY -cubeWidth '@' | |
| calculateForSurface cubeWidth cubeY cubeX '$' | |
| calculateForSurface -cubeWidth cubeY -cubeX '~' | |
| calculateForSurface -cubeX cubeY cubeWidth '#' | |
| calculateForSurface cubeX -cubeWidth -cubeY ';' | |
| calculateForSurface cubeX cubeWidth cubeY '+' | |
| cubeWidth <- 5.0 | |
| horizontalOffset <- 8.0 * cubeWidth | |
| for cubeX in -cubeWidth .. incrementSpeed .. cubeWidth do | |
| for cubeY in -cubeWidth .. incrementSpeed .. cubeWidth do | |
| calculateForSurface cubeX cubeY -cubeWidth '@' | |
| calculateForSurface cubeWidth cubeY cubeX '$' | |
| calculateForSurface -cubeWidth cubeY -cubeX '~' | |
| calculateForSurface -cubeX cubeY cubeWidth '#' | |
| calculateForSurface cubeX -cubeWidth -cubeY ';' | |
| calculateForSurface cubeX cubeWidth cubeY '+' | |
| Console.SetCursorPosition(0, 0) | |
| for k in 0 .. width * height - 1 do | |
| match k % width with | |
| | 0 -> Console.WriteLine() | |
| | _ -> Console.Write(buffer.[k]) | |
| A <- A + 0.05 | |
| B <- B + 0.05 | |
| C <- C + 0.01 | |
| System.Threading.Thread.Sleep(10) | |
| while true do | |
| drawFrame() | |
Author
Brilliant. Love it.
Author
Thanks friend, it's fun to disconnect from work and play with F#.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Cube
ASCII 3D cube renderer written in F#. It draws three spinning cubes in real time using Euler rotations, perspective projection, and a z-buffer.
How it works
1. 3D Rotation (Euler matrices)
Each point
(i, j, k)of the cube is rotated in 3D space using three Euler angles:A(X-axis),B(Y-axis), andC(Z-axis). The combined rotation produces the transformed coordinates:This corresponds to the matrix multiplication Rz(C) · Ry(B) · Rx(A) applied to the vector
(i, j, k).2. Perspective Projection
Once the point is rotated,
distanceFromCamis added toz'to push it away from the camera. Perspective projection then converts 3D coordinates to 2D screen pixels using:K1acts as the focal length: the larger it is, the more zoom is applied. Multiplying byooz(instead of dividing byz) produces the perspective effect — objects further away appear smaller.3. Z-buffer (depth sorting)
To ensure closer faces occlude farther ones, a z-buffer is used: an array parallel to the character buffer that stores the highest
ooz(inverse of z) seen so far for each pixel. Sinceoozis larger for closer points, a character is only written if it exceeds the stored value.4. The Six Faces
Each face is generated by iterating over a 2D grid and fixing the third coordinate to the cube's extreme value (
±cubeWidth). Each face is assigned a distinct ASCII character for visual distinction:cubeZ = −cubeWidth@cubeX = +cubeWidth$cubeX = −cubeWidth~cubeZ = +cubeWidth#cubeY = −cubeWidth;cubeY = +cubeWidth+5. Animation
At the end of each frame the angles are incremented slightly, producing continuous rotation:
All three cubes (sizes 20, 10, and 5) are rendered into the same shared buffer, separated horizontally via
horizontalOffset.Credits
This F# implementation is a port of the original C version of the ASCII 3D cube renderer created by tarantino07.
All credit for the original idea, structure, and mathematical approach goes to the author of:
https://github.com/tarantino07/cube.c