Last active
July 8, 2025 00:00
-
-
Save Denbergvanthijs/7f6936ca90a683d37216fd80f5750e9c to your computer and use it in GitHub Desktop.
3D spinning donut in Python. Based on the pseudocode from: https://www.a1k0n.net/2011/07/20/donut-math.html
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
import numpy as np | |
screen_size = 40 | |
theta_spacing = 0.07 | |
phi_spacing = 0.02 | |
illumination = np.fromiter(".,-~:;=!*#$@", dtype="<U1") | |
A = 1 | |
B = 1 | |
R1 = 1 | |
R2 = 2 | |
K2 = 5 | |
K1 = screen_size * K2 * 3 / (8 * (R1 + R2)) | |
def render_frame(A: float, B: float) -> np.ndarray: | |
""" | |
Returns a frame of the spinning 3D donut. | |
Based on the pseudocode from: https://www.a1k0n.net/2011/07/20/donut-math.html | |
""" | |
cos_A = np.cos(A) | |
sin_A = np.sin(A) | |
cos_B = np.cos(B) | |
sin_B = np.sin(B) | |
output = np.full((screen_size, screen_size), " ") # (40, 40) | |
zbuffer = np.zeros((screen_size, screen_size)) # (40, 40) | |
cos_phi = np.cos(phi := np.arange(0, 2 * np.pi, phi_spacing)) # (315,) | |
sin_phi = np.sin(phi) # (315,) | |
cos_theta = np.cos(theta := np.arange(0, 2 * np.pi, theta_spacing)) # (90,) | |
sin_theta = np.sin(theta) # (90,) | |
circle_x = R2 + R1 * cos_theta # (90,) | |
circle_y = R1 * sin_theta # (90,) | |
x = (np.outer(cos_B * cos_phi + sin_A * sin_B * sin_phi, circle_x) - circle_y * cos_A * sin_B).T # (90, 315) | |
y = (np.outer(sin_B * cos_phi - sin_A * cos_B * sin_phi, circle_x) + circle_y * cos_A * cos_B).T # (90, 315) | |
z = ((K2 + cos_A * np.outer(sin_phi, circle_x)) + circle_y * sin_A).T # (90, 315) | |
ooz = np.reciprocal(z) # Calculates 1/z | |
xp = (screen_size / 2 + K1 * ooz * x).astype(int) # (90, 315) | |
yp = (screen_size / 2 - K1 * ooz * y).astype(int) # (90, 315) | |
L1 = (((np.outer(cos_phi, cos_theta) * sin_B) - cos_A * np.outer(sin_phi, cos_theta)) - sin_A * sin_theta) # (315, 90) | |
L2 = cos_B * (cos_A * sin_theta - np.outer(sin_phi, cos_theta * sin_A)) # (315, 90) | |
L = np.around(((L1 + L2) * 8)).astype(int).T # (90, 315) | |
mask_L = L >= 0 # (90, 315) | |
chars = illumination[L] # (90, 315) | |
for i in range(90): | |
mask = mask_L[i] & (ooz[i] > zbuffer[xp[i], yp[i]]) # (315,) | |
zbuffer[xp[i], yp[i]] = np.where(mask, ooz[i], zbuffer[xp[i], yp[i]]) | |
output[xp[i], yp[i]] = np.where(mask, chars[i], output[xp[i], yp[i]]) | |
return output | |
def pprint(array: np.ndarray) -> None: | |
"""Pretty print the frame.""" | |
print(*[" ".join(row) for row in array], sep="\n") | |
if __name__ == "__main__": | |
for _ in range(screen_size * screen_size): | |
A += theta_spacing | |
B += phi_spacing | |
print("\x1b[H") | |
pprint(render_frame(A, B)) |
wait @The-Final-Apex You use notepad that was my first editor But then I realised The power of neovim and linux so I dumped windows
neovim π , i can agree on the dumping windows, windows is the worst.
In the middle of teh comments for a donut wree having a neovim v notepad argument π π
@korbosoft Thanks for the corrections
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Love this donut lol