Skip to content

Instantly share code, notes, and snippets.

@cjxgm
Last active July 9, 2021 23:00
Show Gist options
  • Save cjxgm/07b473e58766f20d0df4 to your computer and use it in GitHub Desktop.
Save cjxgm/07b473e58766f20d0df4 to your computer and use it in GitHub Desktop.
raymarching ascii-art rendering in 100-lines of lua
---------------------------------------------------------------------------
-- vector
local vector = (function()
local mt = {}
mt.__index = mt
local vector
vector = function(x, y, z)
if type(x) == 'table' then
return setmetatable(x, mt)
end
x = x or 0
y = y or x
z = z or y
return vector{x, y, z}
end
local map = function(f, a, b)
a = vector(a)
b = vector(b)
return vector{ f(a[1], b[1]), f(a[2], b[2]), f(a[3], b[3]) }
end
local sqrt = math.sqrt
mt.__add = function(a, b) return map(function(x, y) return x+y end, a, b) end
mt.__sub = function(a, b) return map(function(x, y) return x-y end, a, b) end
mt.__mul = function(a, b) return map(function(x, y) return x*y end, a, b) end
mt.__div = function(a, b) return map(function(x, y) return x/y end, a, b) end
mt.__neg = function(a ) return map(function(x ) return -x end, a ) end
mt.__concat = function(a, b) return a[1]*b[1] + a[2]*b[2] + a[3]*b[3] end
mt.__len = function(a) return sqrt(a .. a) end
mt.norm = function(a) return a / #a end
return vector
end)()
---------------------------------------------------------------------------
-- distance field renderer
local renderer = (function(max_step, max_radius)
local lerp = function(x, xf, xt, df, dt)
if x < xf then return df end
if x > xt then return dt end
return (x-xf) / (xt-xf) * (dt-df) + df
end
local raymarch = function(est, origin, dir)
local dist = 0
for i=1,max_step do
local d = est(dir * dist + origin)
if d < 1e-5 then return lerp(i, 1, max_step, 0, 1) end
dist = dist + d
if dist > max_radius then break end
end
end
local rad, cos, sin, pow = math.rad, math.cos, math.sin, math.pow
local write = io.write
local aa = function(x)
local grays = [==[ .'`^",:;Il!i><~+_-?][}{1)(|\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$]==]
x = math.floor(lerp(x, 0, 1, 1, grays:len()))
return grays:sub(x, x)
end
return function(estimator, size, fov, eye)
for y=1,size.h do
for x=1,size.w do
local cx = rad( lerp(x, 1, size.w, -fov.x/2, fov.x/2))
local cy = rad(-lerp(y, 1, size.h, -fov.y/2, fov.y/2))
local dir = vector(cos(cy)*sin(cx), sin(cy), -cos(cy)*cos(cx))
local x = raymarch(estimator, vector(eye), dir)
if x then x = pow(1-x, 10) else x = 0 end
write(aa(x))
end
write '\n'
end
end
end)(100, 50)
---------------------------------------------------------------------------
-- main
local estimator = (function()
local s2p = vector(2, 0, 0)
local min, max = math.min, math.max
return function(p)
local ns1 = 1 - #p
local s2 = #(p-s2p) - 2
local p = p[2] + 1
return max(min(s2, p), ns1)
end
end)()
local scale=1.4
renderer(estimator, {w=60*1.7*scale, h=30*scale}, {x=160, y=90}, {-0.5, 0, 2})
---------------------------------------------------------------------------
-- vector
local vector = (function()
local mt = {}
mt.__index = mt
local vector
vector = function(x, y, z)
if type(x) == 'table' then
return setmetatable(x, mt)
end
x = x or 0
y = y or x
z = z or y
return vector{x, y, z}
end
local map = function(f, a, b)
a = vector(a)
b = vector(b)
return vector{ f(a[1], b[1]), f(a[2], b[2]), f(a[3], b[3]) }
end
local sqrt = math.sqrt
mt.__add = function(a, b) return map(function(x, y) return x+y end, a, b) end
mt.__sub = function(a, b) return map(function(x, y) return x-y end, a, b) end
mt.__mul = function(a, b) return map(function(x, y) return x*y end, a, b) end
mt.__div = function(a, b) return map(function(x, y) return x/y end, a, b) end
mt.__neg = function(a ) return map(function(x ) return -x end, a ) end
mt.__concat = function(a, b) return a[1]*b[1] + a[2]*b[2] + a[3]*b[3] end
mt.__len = function(a) return sqrt(a .. a) end
mt.norm = function(a) return a / #a end
return vector
end)()
---------------------------------------------------------------------------
-- distance field renderer
local renderer = (function(max_step, max_radius)
local lerp = function(x, xf, xt, df, dt)
if x < xf then return df end
if x > xt then return dt end
return (x-xf) / (xt-xf) * (dt-df) + df
end
local raymarch = function(est, origin, dir)
local dist = 0
for i=1,max_step do
local d = est(dir * dist + origin)
if d < 1e-5 then return lerp(i, 1, max_step, 0, 1) end
dist = dist + d
if dist > max_radius then break end
end
end
local rad, cos, sin, pow, floor = math.rad, math.cos, math.sin, math.pow, math.floor
local fmt = string.format
local write = io.write
local aa = function(x)
return fmt("\x1b[48;5;%dm \x1b[0m", floor(lerp(x, 0, 1, 232, 255)))
end
return function(estimator, size, fov, eye)
for y=1,size.h do
for x=1,size.w do
local cx = rad( lerp(x, 1, size.w, -fov.x/2, fov.x/2))
local cy = rad(-lerp(y, 1, size.h, -fov.y/2, fov.y/2))
local dir = vector(cos(cy)*sin(cx), sin(cy), -cos(cy)*cos(cx))
local x = raymarch(estimator, vector(eye), dir)
if x then x = pow(1-x, 10) else x = 0 end
write(aa(x))
end
write '\n'
end
end
end)(100, 50)
---------------------------------------------------------------------------
-- main
local estimator = (function()
local s2p = vector(2, 0, 0)
local min, max = math.min, math.max
return function(p)
local ns1 = 1 - #p
local s2 = #(p-s2p) - 2
local p = p[2] + 1
return max(min(s2, p), ns1)
end
end)()
local scale=1.4
renderer(estimator, {w=60*1.7*scale, h=30*scale}, {x=160, y=90}, {-0.5, 0, 2})
...'''`````''''..
.'`^^",::;;;IIII;;;::,"^^`'.
.'^",;;Il!!!>>>>>>>>>>>>!!llI;:,"^'.
.`^,:Il!!>~~~++++--------++++~~>>!!lI:"^'.
'^":Il!>~~++---[[[[[[[[[[[[[[[[---++~~>!lI:"`.
`''. !>~~+--[[[{{{{{(((((((((({{{{{[[[--+~~>!I:"`.
;:,"^`'. --[[{{{((((((//////////((((({{{[[[-++~>!I:^
>!!llI;:"^'. [{{((((//////rrrrrrrr//////(((({{[[-++~!l;
~~~>>!lI;:"^' (((////rrrrrrrrrrrrrrrrrr////((({{[[-+~>!
---++~~>!lI;,^'(///rrrrruuuuuuuuuuuuuurrrrr///((({{[-+~>
[[[[---++~>!lI:^'/rrrruuuuuuuuYYYYuuuuuuuurrrr///(({{[-+~
{{{{[[[--+~>!lI:^rrruuuuuYYYYYYYYYYYYYYuuuuurrrr//(({{[-+
(((({{[[[-+~~!l;,'uuuuuYYYYYYYYYYYYYYYYYYuuuuurrr//(({{[-
///(((({{[[-+~>!I:^uuuYYYYYYLLLLLLLLLLYYYYYYuuuurrr//(({[[
r////((({{[--+~>l;"uuYYYYYLLLLLLLLLLLLLLYYYYYuuurrr//(({{[
rrrr//((({[[-+~>!I,uYYYYYLLLLLmmmmmLLLLLLYYYYuuuurrr//(({[
uurr///(({{[-++>!I,uYYYYLLLLLmmmmmmmmLLLLYYYYYuuurrr//(({[
uurr///(({{[-++>!I,uYYYYLLLLLmmmmmmmmLLLLYYYYYuuurrr//(({[
rrrr//((({[[-+~>!I,uYYYYYLLLLLmmmmmLLLLLLYYYYuuuurrr//(({[
r////((({{[--+~>l;"uuYYYYYLLLLLLLLLLLLLLYYYYYuuurrr//(({{[
///(((({{[[-+~>!I:^uuuYYYYYYLLLLLLLLLLYYYYYYuuuurrr//(({[[
(((({{[[[-+~~!l;,'uuuuuYYYYYYYYYYYYYYYYYYuuuuurrr//(({{[-
{{{{[[[--+~>!lI:^rrruuuuuYYYYYYYYYYYYYYuuuuurrrr//(({{[-+
[[[[---++~>!lI:^'/rrrruuuuuuuuYYYYuuuuuuuurrrr///(({{[-+~
---++~~>!lI;,^'(///rrrrruuuuuuuuuuuuuurrrrr///((({{[-+~>
..................................................................................... ~~~>>!lI;:"^' (((////rrrrrrrrrrrrrrrrrr////((({{[[-+~>!
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''.>!!llI;:"^'. [{{((((//////rrrrrrrr//////(((({{[[-++~!l;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^``` ;:,"^`'. --[[{{{((((((//////////((((({{{[[[-++~>!I:^
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,""^``''. !>~~+--[[[{{{{{(((((((((({{{{{[[[--+~~>!I:"`.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::^ '^":Il!>~~++---[[[[[[[[[[[[[[[[---++~~>!lI:"`. "
llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllII.`^,:Il!!>~~~++++--------++++~~>>!!lI:"^'.IIll
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:",;;Il!!!>>>>>>>>>>>>!!llI;:,"l!!!!!!!!!
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;;;IIII;;;>>>>>>>>>>>>>>>>>>>>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
----------------------------------------------------------------------------------------------------------------------------------------------
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{

fontsize:

fontsize

result:

result

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment