# Rcjp's Weblog

## June 4, 2006

### Raytracing in LUA

Filed under: misc — rcjp @ 3:54 pm

Just learning lua so this might not be correct code; but I thought I’d try a quick translation of a ray tracing program from Common Lisp into lua using some simulated OOP…

``` -- -- Based on Paul Graham's raytrace -- function math.round(num, idp) local mult = 10^(idp or 0) return math.floor(num * mult + 0.5) / mult end function magnitude(pt) return math.sqrt(pt[1]^2 + pt[2]^2 + pt[3]^2) end function unit_vector(v) local mag = magnitude(v) return {v[1]/mag, v[2]/mag, v[3]/mag} end function distance(p1, p2) return magnitude{p1[1] - p2[1], p1[2] - p2[2], p1[3] - p2[3]} end function minroot(a, b, c) if a == 0 then return -c/b else local disc = b^2 - 4*a*c if disc < 0 then return false else local discrt = math.sqrt(disc) return math.min((-b + discrt)/(2*a), (-b - discrt)/(2*a)) end end end -- -- Sphere functions -- sphere = {} sphere.mt = {} -- do i need to reset this?? sphere.mt.__index = sphere -- sphere.mt.__tostring = sphere.tostring function sphere.new (o) setmetatable(o, sphere.mt) return o end function sphere.tostring() print('the sphere') end function sphere:intersect(pt, ray) -- Calculate if/where a ray starting from point pt hits this sphere s local n = minroot(ray[1]^2 + ray[2]^2 + ray[3]^2, 2*((pt[1] - self.c[1])*ray[1] + (pt[2] - self.c[2])*ray[2] + (pt[3] - self.c[3])*ray[3]), (pt[1] - self.c[1])^2 + (pt[2] - self.c[2])^2 + (pt[3] - self.c[3])^2 - self.r^2) if n then -- minroot might return false if there is no root return {pt[1] + n*ray[1], pt[2] + n*ray[2], pt[3] + n*ray[3]} else return false end end function sphere:normal(pt) return unit_vector {self.c[1] - pt[1], self.c[2] - pt[2], self.c[3] - pt[3]} end function sphere:lambert(inter, light) local pt = self:normal(inter) return math.max(0, light[1]*pt[1] + light[2]*pt[2] + light[3]*pt[3]) end function first_hit(eye, ray) local hitsomething = false local hit = false local mindist local surface = false for i,shape in pairs(world) do local inter = shape.intersect(shape, eye, ray) if inter then local d = distance(inter, eye) if hitsomething == false or d < mindist then -- found nearer shape hit, surface = inter, shape mindist = d hitsomething = true end end end return surface, hit end function sendray(eye, ray) local reflect = 0.0 -- defaults to background if first_hit failed s, inter = first_hit(eye, ray) -- ?? if isinstance(s, shape): -- if getmetatable(s) == sphere if s then -- print("here s=",s) reflect = s:lambert(inter, ray) * s.colour else reflect = 0 end return reflect end function colour_at(x, y) local ray = unit_vector {x - eye[1], y - eye[2], 0 - eye[3]} local reflected = sendray(eye, ray) return math.round(reflected*255) end function tracer(filename, res) f = assert(io.open(filename,"w")) print 'Writing data to ', filename f:write(string.format("P2 %d %d 255\n", res*100+1, res*100+1)) for y = -50*res, 50*res do for x = -50*res, 50*res do f:write(string.format("%d ", colour_at(x/res, y/res))) end end f:close() end world = {} eye = {0, 0, 200} function raytest(option) res = option.res or 1 world = { sphere.new{c={ 0.0, -300.0, -1200.0}, r=200.0, colour=0.8}, sphere.new{c={-80.0, -150.0, -1200.0}, r=200.0, colour=0.7}, sphere.new{c={ 70.0, -100.0, -1200.0}, r=200.0, colour=0.9} } for x = -2, 2 do for z = 2, 7 do table.insert(world, sphere.new {c = {x*200, 300, z*-400}, r = 40.0, colour = 0.75}) end end tracer('luaspheres.pgm', res) end raytest{} ```