Heya everyone.
So I was kinda bored, I had nothing to do, and no project to work on, like usual.
But today, I stumbled on donut.c.
for those who dont know, donut.c is an ASCII donut shape/torus animation fully rendered inside the console.
I was absolutely thrilled, excited and amazed, this was the pinnacle of maths, programming, and useless projects!
I looked more into it and found out that the original creator of donut.c(a1k0n) made a website documenting the maths and the process of making donut.c, heres the website, full props to him.
https://www.a1k0n.net/2011/07/20/donut-math.html
I scrolled down to the bottom, and found pseudocode on how to make it, in c of course.
I didnt understand most of the maths, but since I started learning c++ recently(like 1 1/2 months?), I thought to myself:
“Why don’t I recreate this in roblox?”
And so this project started, I went down to the psuedo code, and literally started transcribing everything into luau, every line(except the comments lul), I translated it to luau.
I tried to get as close as possible to the actual psuedo code, and I think I got pretty close.
I had to change some bits up for it to fit better inside the studio console, and some minor changes for it to work inside luau.
But at last, I finished, ran it, and it worked in roblox, what a surprise.
Anyway, here is the code I transcribed:
local screen_height, screen_width = 20, 50;
local zOffset = 7; -- Distance between camera and donut, without this, it looks weird lul
local theta_spacing = 0.07;
local phi_spacing = 0.02;
local two_pi = 2 * math.pi;
local R1 = 1;
local R2 = 2;
local K2 = 5;
local K1 = screen_width * K2 * 3 / (8 * (R1 + R2));
local output = table.create(screen_width + 1)
local zbuffer = table.create(screen_width + 1)
for x = 0, screen_width do
output[x] = table.create(screen_height + 1, ' ')
zbuffer[x] = table.create(screen_height + 1, 0)
end
function render_frame(A, B)
local cosA, sinA = math.cos(A), math.sin(A);
local cosB, sinB = math.cos(B), math.sin(B);
for x = 0, screen_width do
for y = 0, screen_height do
output[x][y] = ' '
zbuffer[x][y] = 0
end
end
local theta = 0;
while theta < two_pi do
local costheta, sintheta = math.cos(theta), math.sin(theta);
local phi = 0;
while phi < two_pi do
local cosphi, sinphi = math.cos(phi), math.sin(phi);
local circlex = R2 + R1 * costheta;
local circley = R1 * sintheta;
local x = circlex * (cosB * cosphi + sinA * sinB * sinphi)
- circley * cosA * sinB;
local y = circlex * (sinB * cosphi - sinA * cosB * sinphi)
+ circley * cosA * cosB;
local z = K2 + cosA*circlex*sinphi + circley*sinA + zOffset;
local ooz = 1/z;
local xp = math.clamp(math.floor(screen_width / 2 + K1 * ooz * x), 0, screen_width);
local yp = math.clamp(math.floor(screen_height / 2 - K1 * ooz * y), 0, screen_height);
local L = cosphi * costheta * sinB - cosA * costheta * sinphi -
sinA*sintheta + cosB*(cosA * sintheta - costheta * sinA * sinphi);
if L > 0 then
if ooz > zbuffer[xp][yp] then
zbuffer[xp][yp] = ooz;
local luminance_index = math.floor(L * 8);
output[xp][yp] = string.sub(".,-~:;=!*#$@", luminance_index + 1, luminance_index + 1)
end
end
phi = phi + phi_spacing
end
theta = theta + theta_spacing
end
local frame_lines = {}
for y = 0, screen_height - 1 do
local line = {}
for x = 0, screen_width - 1 do
line[x + 1] = output[x][y]
end
frame_lines[y + 1] = table.concat(line)
end
return table.concat(frame_lines, "\n")
end
local A, B = 0, 0
while true do
local frame = render_frame(A, B)
print("\27" .. frame)
A = A + 0.07
B = B + 0.02
task.wait(0.05)
end
You can test it out yourself, but it might look different on your device, try changing the top settings, it might fix it.
Of course my script isn’t optimised and looks horrid, but it works.
I might look into how this works more, and maybe make something similar of my own(cube.c?).
Anyway, full props to a1k0n for making this, this is peak, go check out how it all works, the pseudocode I translated from is at the bottom of the website.
https://www.a1k0n.net/2011/07/20/donut-math.html
Anyway, thats basically it, bless you all!