Donut.c in roblox luau

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!

3 Likes

This is cool, nice work. (Word Limit)

1 Like