local sqrt = math.sqrt
local huge = math.huge

local delta = 1
while delta * delta + 1 ~= 1 do
  delta = delta * 0.5
end

local function length(x, y, z)  return sqrt(x*x + y*y + z*z) end
local function vlen(v)          return length(v[1], v[2], v[3]) end
local function mul(c, x, y, z)  return c*x, c*y, c*z end
local function unitise(x, y, z) return mul(1/length(x, y, z), x, y, z) end
local function dot(x1, y1, z1, x2, y2, z2)
  return x1*x2 + y1*y2 + z1*z2
end

local function vsub(a, b)        return a[1] - b[1], a[2] - b[2], a[3] - b[3] end
local function vdot(a, b)        return dot(a[1], a[2], a[3], b[1], b[2], b[3]) end


local sphere = {}
function sphere:new(centre, radius)
  self.__index = self
  return setmetatable({centre=centre, radius=radius}, self)
end

local function sphere_distance(self, origin, dir)
  local vx, vy, vz = vsub(self.centre, origin)
  local b = dot(vx, vy, vz, dir[1], dir[2], dir[3])
  local r = self.radius
  local disc = r*r + b*b - vx*vx-vy*vy-vz*vz
  if disc < 0 then return huge end
  local d = sqrt(disc)
  local t2 = b + d
  if t2 < 0 then return huge end
  local t1 = b - d
  return t1 > 0 and t1 or t2
end

function sphere:intersect(origin, dir, best)
  local lambda = sphere_distance(self, origin, dir)
  if lambda < best[1] then
    local c = self.centre
    best[1] = lambda
    local b2 = best[2]
    b2[1], b2[2], b2[3] =
      unitise(
        origin[1] - c[1] + lambda * dir[1],
        origin[2] - c[2] + lambda * dir[2],
        origin[3] - c[3] + lambda * dir[3])
  end
end

local group = {}
function group:new(bound)
  self.__index = self
  return setmetatable({bound=bound, children={}}, self)
end

function group:add(s)
  self.children[#self.children+1] = s
end

function group:intersect(origin, dir, best)
  local lambda = sphere_distance(self.bound, origin, dir)
  if lambda < best[1] then
    for _, c in ipairs(self.children) do
      c:intersect(origin, dir, best)
    end
  end
end

local hit = { 0, 0, 0 }
local ilight
local best = { huge, { 0, 0, 0 } }

local function ray_trace(light, camera, dir, scene)
  best[1] = huge
  scene:intersect(camera, dir, best)
  local b1 = best[1]
  if b1 == huge then return 0 end
  local b2 = best[2]
  local g = vdot(b2, light)
  if g >= 0 then return 0 end
  hit[1] = camera[1] + b1*dir[1] + delta*b2[1]
  hit[2] = camera[2] + b1*dir[2] + delta*b2[2]
  hit[3] = camera[3] + b1*dir[3] + delta*b2[3]
  best[1] = huge
  scene:intersect(hit, ilight, best)
  if best[1] == huge then
    return -g
  else
    return 0
  end
end

local function create(level, centre, radius)
  local s = sphere:new(centre, radius)
  if level == 1 then return s end
  local gr = group:new(sphere:new(centre, 3*radius))
  gr:add(s)
  local rn = 3*radius/sqrt(12)
  for dz = -1,1,2 do
    for dx = -1,1,2 do
      gr:add(create(level-1, { centre[1] + rn*dx, centre[2] + rn, centre[3] + rn*dz }, radius*0.5))
    end
  end
  return gr
end


local level, n, ss = 9, 32, 4
local iss = 1/ss
local gf = 255/(ss*ss)

io.write(("P5\n%d %d\n255\n"):format(n, n))
local light = { unitise(-1, -3, 2) }
ilight = { -light[1], -light[2], -light[3] }
local camera = { 0, 0, -4 }
local dir = { 0, 0, 0 }

local scene = create(level, {0, -1, 0}, 1)

for y = n/2-1, -n/2, -1 do
  for x = -n/2, n/2-1 do
    local g = 0
    for d = y, y+.99, iss do
      for e = x, x+.99, iss do
        dir[1], dir[2], dir[3] = unitise(e, d, n)
        g = g + ray_trace(light, camera, dir, scene) 
      end
    end
    io.write(string.char(math.floor(0.5 + g*gf)))
  end
end

