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{}

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: