Post by tsh73 on Dec 7, 2023 14:02:41 GMT -5
converted from Python
vgel.me/posts/donut/
Very funny reading, one could read along and build a program from scratch.
Some Python things are not possible in BASIC, has to improvize on the go.
(Got several unfinished-but-working versions as it go.)
But indeed LB lacks speed
so I end up storing frames first, than showing it in texteditor
(it could print to mainwin, but picture is optimised for white on black).
I wonder if it could be made somewhat faster though.
vgel.me/posts/donut/
Very funny reading, one could read along and build a program from scratch.
Some Python things are not possible in BASIC, has to improvize on the go.
(Got several unfinished-but-working versions as it go.)
But indeed LB lacks speed
so I end up storing frames first, than showing it in texteditor
(it could print to mainwin, but picture is optimised for white on black).
I wonder if it could be made somewhat faster though.
'tsh73 Dec 2023
'from python,
'https://vgel.me/posts/donut/
global theta, ss, cc 'LB works too slow. So I keep single theta per frame
global nt.x, nt.y, nt.z 'for returns from function normal
'ansver for "slow" is to build array of frames!
' theta is second *2, so 2 pi is 3 sec, at 30 fps. Needs about 100 frames
pi = acs(-1)
'nFrames = 2*pi/2*30
'print nFrames '94xx
'end
nFrames = 94*2
dim frame$(nFrames)
'build frames (slow)
for frame = 0 to nFrames-1
SCAN
'# loop over each position and sample a character
frameChars$ = ""
'theta = time$("ms")/1000 * 2 'global, not to be changed per frame
theta = frame/30
ss=sin(theta):cc=cos(theta)
for y = 0 to 19
'# ...and corrected for aspect ratio range (for y)
remapped.y = (y / 20 * 2 - 1) * (2 * 20/80)
for x = 0 to 80
'# remap to -1..1 range (for x)...
remapped.x = x / 80 * 2 - 1
frameChars$= frameChars$+sample$(remapped.x, remapped.y)
next
frameChars$= frameChars$+chr$(13)
next
'# print out a control sequence to clear the terminal, then the frame.
'cls
'print frameChars$
print "Making ";frame;" of ";nFrames;"..."
frame$(frame)=frameChars$
next
'draw ready frames (fast!)
'alas I need it white on black, so mainwin wouldn't do
'so create window
WindowWidth = 800
WindowHeight = 500
UpperLeftX=int((DisplayWidth-WindowWidth)/2)
UpperLeftY=int((DisplayHeight-WindowHeight)/2)
BackgroundColor$="black"
ForegroundColor$="white"
TexteditorColor$="black"
texteditor #disp.txt, 1, 1, 800, 500
open "display" for window_nf as #disp
#disp "trapclose [quit]"
#disp "font courier_new"
frame = 0
while 1
'# print out a control sequence to clear the terminal, then the frame.
print #disp.txt, "!cls"
print #disp.txt, frame$(frame)
frame = (frame+1) mod nFrames
'# cap at 30fps
timer 30, [tick]
wait
[tick]
timer 0
wend
[quit]
close #disp
end
function sample$(x, y)
'# start `z` far back from the scene, which is centered at 0, 0, 0,
'# so nothing clips
z = -10
'# we'll step at most 30 steps before assuming we missed the scene
for sstep =0 to 29
'# calculate the angle based on time, to animate the donut spinning
'theta = time$("seconds") * 2
''' now global, since it is too slow to keep up
'# rotate the input coordinates, which is equivalent to rotating the sdf
t.x = x * cc - z * ss
t.z = x * ss + z * cc
d = donut(t.x, y, t.z)
'# test against 0.01, not 0: we're a little more forgiving with the distance
'# in 3D for faster convergence
if d <= 0.01 then
call normal t.x, y, t.z 'sets globals nt.x, nt.y, nt.z
'print nt.x, nt.y, nt.z
is.lit = nt.y < -0.15
is.frosted = nt.z < -0.5
if is.frosted then
if is.lit then
sample$= "@"
else
sample$= "#"
end if
else
if is.lit then
sample$= "="
else
sample$= "."
end if
end if
exit function
else
'# didn't hit anything yet, move the ray forward
'# we can safely move forward by `d` without hitting anything since we know
'# that's the distance to the scene
z=z+d
end if
next
'# we didn't hit anything after 30 steps, return the background
sample$= " "
end function
function circle(x, y)
'# since the range of x is -1..1, the circle's radius will be 40%,
'# meaning the circle's diameter is 40% of the screen
radius = 0.4
'# calculate the distance from the center of the screen and subtract the
'# radius, so d will be < 0 inside the circle, 0 on the edge, and > 0 outside
circle= sqr(x*x + y*y) - radius
end function
function donut2d(x, y)
'# same radius as before, though the donut will appear larger as
'# half the thickness is outside this radius
radius = 0.4
'# how thick the donut will be
thickness = 0.3
'# take the abs of the circle calculation from before, subtracting
'# `thickness / 2`. `abs(...)` will be 0 on the edge of the circle, and
'# increase as you move away. therefore, `abs(...) - thickness / 2` will
'# be = 0 only `thickness / 2` units away from the circle's edge on either
'# side, giving a donut with a total width of `thickness`
donut2d=abs(sqr(x*x + y*y) - radius) - thickness / 2
end function
function sphere(x, y, z)
radius = 0.4
sphere=sqr(x*x + y*y + z*z) - radius
end function
function donut(x, y, z)
radius = 0.4
thickness = 0.3
'# first, we get the distance from the center and subtract the radius,
'# just like the 2d donut.
'# this value is the distance from the edge of the xy circle along a line
'# drawn between [x, y, 0] and [0, 0, 0] (the center of the donut).
xy.d = sqr(x*x + y*y) - radius
'# now we need to consider z, which, since we're evaluating the donut at
'# [0, 0, 0], is the distance orthogonal (on the z axis) to that
'# [x, y, 0]..[0, 0, 0] line.
'# we can use these two values in the usual euclidean distance function to get
'# the 3D version of our 2D donut "distance from edge" value.
d = sqr(xy.d*xy.d + z*z)
'# then, we subtract `thickness / 2` as before to get the signed distance,
'# just like in 2D.
donut= d - thickness / 2
end function
sub normal x, y, z
'# an arbitrary small amount to offset around the point
eps = 0.001
'# calculate each axis independently
n.x = donut(x + eps, y, z) - donut(x - eps, y, z)
n.y = donut(x, y + eps, z) - donut(x, y - eps, z)
n.z = donut(x, y, z + eps) - donut(x, y, z - eps)
'# normalize the result to length = 1
norm = sqr(n.x*n.x + n.y*n.y + n.z*n.z)
'returns 3 values in globals
nt.x=n.x / norm
nt.y=n.y / norm
nt.z=n.z / norm
end sub