├── README.md └── donut.py /README.md: -------------------------------------------------------------------------------- 1 | # Donut 2 | 3D ASCII donut using Python with Pygame 3 | 4 | This code is inspired by Andy Sloane blog post where he explained how he wrote C code for rotating ASCII donut. 5 | https://www.a1k0n.net/2011/07/20/donut-math.html 6 | 7 | Faster and improved version: 8 | https://github.com/codegiovanni/Donut_2.0 9 | -------------------------------------------------------------------------------- /donut.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | import colorsys 4 | 5 | pygame.init() 6 | 7 | white = (255, 255, 255) 8 | black = (0, 0, 0) 9 | hue = 0 10 | 11 | WIDTH = 1920 12 | HEIGHT = 1080 13 | 14 | x_start, y_start = 0, 0 15 | 16 | x_separator = 10 17 | y_separator = 20 18 | 19 | rows = HEIGHT // y_separator 20 | columns = WIDTH // x_separator 21 | screen_size = rows * columns 22 | 23 | x_offset = columns / 2 24 | y_offset = rows / 2 25 | 26 | A, B = 0, 0 # rotating animation 27 | 28 | theta_spacing = 10 29 | phi_spacing = 1 # for faster rotation change to 2, 3 or more, but first change 86, 87 lines as commented 30 | 31 | chars = ".,-~:;=!*#$@" # luminance index 32 | 33 | screen = pygame.display.set_mode((WIDTH, HEIGHT)) 34 | 35 | display_surface = pygame.display.set_mode((WIDTH, HEIGHT)) 36 | # display_surface = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) 37 | pygame.display.set_caption('Donut') 38 | font = pygame.font.SysFont('Arial', 18, bold=True) 39 | 40 | def hsv2rgb(h, s, v): 41 | return tuple(round(i * 255) for i in colorsys.hsv_to_rgb(h, s, v)) 42 | 43 | 44 | def text_display(letter, x_start, y_start): 45 | text = font.render(str(letter), True, hsv2rgb(hue, 1, 1)) 46 | display_surface.blit(text, (x_start, y_start)) 47 | 48 | # def text_display(letter, x_start, y_start): 49 | # text = font.render(str(letter), True, white) 50 | # display_surface.blit(text, (x_start, y_start)) 51 | 52 | 53 | run = True 54 | while run: 55 | 56 | screen.fill((black)) 57 | 58 | z = [0] * screen_size # Donut. Fills donut space 59 | b = [' '] * screen_size # Background. Fills empty space 60 | 61 | for j in range(0, 628, theta_spacing): # from 0 to 2pi 62 | for i in range(0, 628, phi_spacing): # from 0 to 2pi 63 | c = math.sin(i) 64 | d = math.cos(j) 65 | e = math.sin(A) 66 | f = math.sin(j) 67 | g = math.cos(A) 68 | h = d + 2 69 | D = 1 / (c * h * e + f * g + 5) 70 | l = math.cos(i) 71 | m = math.cos(B) 72 | n = math.sin(B) 73 | t = c * h * g - f * e 74 | x = int(x_offset + 40 * D * (l * h * m - t * n)) # 3D x coordinate after rotation 75 | y = int(y_offset + 20 * D * (l * h * n + t * m)) # 3D y coordinate after rotation 76 | o = int(x + columns * y) 77 | N = int(8 * ((f * e - c * d * g) * m - c * d * e - f * g - l * d * n)) # luminance index 78 | if rows > y and y > 0 and x > 0 and columns > x and D > z[o]: 79 | z[o] = D 80 | b[o] = chars[N if N > 0 else 0] 81 | 82 | if y_start == rows * y_separator - y_separator: 83 | y_start = 0 84 | 85 | for i in range(len(b)): 86 | A += 0.00004 # for faster rotation change to bigger value 87 | B += 0.00002 # for faster rotation change to bigger value 88 | if i == 0 or i % columns: 89 | text_display(b[i], x_start, y_start) 90 | x_start += x_separator 91 | else: 92 | y_start += y_separator 93 | x_start = 0 94 | text_display(b[i], x_start, y_start) 95 | x_start += x_separator 96 | 97 | 98 | pygame.display.update() 99 | 100 | hue += 0.005 101 | 102 | for event in pygame.event.get(): 103 | if event.type == pygame.QUIT: 104 | run = False 105 | if event.type == pygame.KEYDOWN: 106 | if event.key == pygame.K_ESCAPE: 107 | run = False 108 | 109 | --------------------------------------------------------------------------------