106 lines
3.1 KiB
Lua
106 lines
3.1 KiB
Lua
|
#!/usr/bin/env lua5.1
|
||
|
-- -*- coding: utf-8 -*-
|
||
|
|
||
|
-- 3D “donut” shape rendering using floating-point math
|
||
|
-- see <https://www.a1k0n.net/2011/07/20/donut-math.html>
|
||
|
|
||
|
-- cargo-culted by erle 2023-09-18
|
||
|
|
||
|
local theta_spacing = 0.01 -- 0.07
|
||
|
local phi_spacing = 0.002 -- 0.02
|
||
|
|
||
|
local R1 = 1
|
||
|
local R2 = 2
|
||
|
local K2 = 5
|
||
|
|
||
|
local screen_height = 256
|
||
|
local screen_width = 256
|
||
|
|
||
|
local K1 = screen_width * K2 * 3 / ( 8 * ( R1 + R2 ) )
|
||
|
|
||
|
local output = {}
|
||
|
local zbuffer = {}
|
||
|
|
||
|
local grey = { 120, 120, 120 }
|
||
|
local gray = { 136, 136, 136 }
|
||
|
|
||
|
for y = 1,screen_height,1 do
|
||
|
output[y] = {}
|
||
|
zbuffer[y] = {}
|
||
|
for x = 1,screen_width,1 do
|
||
|
local hori = math.floor( ( (y - 1) / 32 ) % 2 )
|
||
|
local vert = math.floor( ( (x - 1) / 32 ) % 2 )
|
||
|
output[y][x] = hori ~= vert and grey or gray
|
||
|
zbuffer[y][x] = 0
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function render_frame(A, B)
|
||
|
-- precompute sines and cosines of A and B
|
||
|
local cosA = math.cos(A)
|
||
|
local sinA = math.sin(A)
|
||
|
local cosB = math.cos(B)
|
||
|
local sinB = math.sin(B)
|
||
|
|
||
|
-- theta goas around the cross-sectional circle of a torus
|
||
|
local theta = 0
|
||
|
while theta <= 2*math.pi do
|
||
|
if ( theta < 2*math.pi * 1/8 ) or ( theta > 2*math.pi * 7/8 ) then
|
||
|
theta = theta + (theta_spacing * 16)
|
||
|
else
|
||
|
theta = theta + theta_spacing
|
||
|
end
|
||
|
-- precompute sines and cosines of theta
|
||
|
local costheta = math.cos(theta)
|
||
|
local sintheta = math.sin(theta)
|
||
|
|
||
|
-- phi goes around the center of revolution of a torus
|
||
|
local phi = 0
|
||
|
while phi <= 2*math.pi do
|
||
|
if ( phi > 2*math.pi * 3/8 ) and ( phi < 2*math.pi * 5/8 ) then
|
||
|
phi = phi + (phi_spacing * 128)
|
||
|
else
|
||
|
phi = phi + phi_spacing
|
||
|
end
|
||
|
-- precompute sines and cosines of phi
|
||
|
local cosphi = math.cos(phi)
|
||
|
local sinphi = math.sin(phi)
|
||
|
|
||
|
-- 2D (x, y) coordinates of the circle, before revolving
|
||
|
local circlex = R2 + R1*costheta
|
||
|
local circley = R1*sintheta
|
||
|
|
||
|
-- 3D (x, y, z) coordinates after rotation
|
||
|
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
|
||
|
|
||
|
local ooz = 1/z
|
||
|
|
||
|
-- x and y projection
|
||
|
local xp = math.floor(screen_width/2 + K1*ooz*x)
|
||
|
local yp = math.floor(screen_height/2 + K1*ooz*y)
|
||
|
|
||
|
-- calculate luminance
|
||
|
local L = cosphi*costheta*sinB - cosA*costheta*sinphi - sinA*sintheta + cosB*( cosA*sintheta - costheta*sinA*sinphi )
|
||
|
-- if (L > 0) then
|
||
|
if (true) then
|
||
|
if (ooz > zbuffer[yp][xp]) then
|
||
|
zbuffer[yp][xp] = ooz
|
||
|
local luminance = math.max( math.ceil( L * 180 ), 0 )
|
||
|
-- luminance is now in the range 0 to 255
|
||
|
r = math.ceil( (luminance + xp) / 2 )
|
||
|
g = math.ceil( (luminance + yp) / 2 )
|
||
|
b = math.ceil( (luminance + xp + yp) / 3 )
|
||
|
output[yp][xp] = { r, g, b }
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
dofile('init.lua')
|
||
|
|
||
|
render_frame(-0.7, 0.7)
|
||
|
tga_encoder.image(output):save("donut.tga")
|