Lignum

Raycasting API

Dec 14th, 2014
448
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 5.67 KB | None | 0 0
  1. local BASE_16 = "0123456789abcdef"
  2.  
  3. --# Linearly interpolates x and y by t.
  4. local function lerp(x, y, t)
  5.     return (1 - t) * x + t * y
  6. end
  7.  
  8. --# Calculates c from a and b.
  9. local function hypot(a, b)
  10.     --# c^2=a^2+b^2
  11.     --# c=sqrt(a^2+b^2)
  12.     return math.sqrt(a^2+b^2)
  13. end
  14.  
  15. local Raycaster = {
  16.     new = function(self, mapWidth, mapHeight)
  17.         self:clearTiles()
  18.  
  19.         self.width = mapWidth
  20.         self.height = mapHeight
  21.  
  22.         self.cameraX = 0
  23.         self.cameraY = 0
  24.         self.cameraAngle = 0
  25.         self.cameraFov = 60
  26.  
  27.         self.wallHeight = 1
  28.  
  29.         self.skyColour = colours.black
  30.  
  31.         for y=1,mapHeight do
  32.             self.tiles[y] = {}
  33.             for x=1,mapWidth do
  34.                 self.tiles[y][x] = 0
  35.             end
  36.         end
  37.     end,
  38.  
  39.     setCameraTransform = function(self, x, y, angle)
  40.         self.cameraX = x
  41.         self.cameraY = y
  42.         self.cameraAngle = angle
  43.     end,
  44.  
  45.     setCameraFOV = function(self, fov)
  46.         self.cameraFov = fov
  47.     end,
  48.  
  49.     getCameraTransform = function(self)
  50.         return self.cameraX, self.cameraY, self.cameraAngle
  51.     end,
  52.  
  53.     setWallHeight = function(self, newHeight)
  54.         self.wallHeight = newHeight
  55.     end,
  56.  
  57.     getWallHeight = function(self)
  58.         return self.wallHeight
  59.     end,
  60.  
  61.     setSkyColour = function(self, colour)
  62.         self.skyColour = colour
  63.     end,
  64.  
  65.     getSkyColour = function(self)
  66.         return self.skyColour
  67.     end,
  68.  
  69.     setSkyColor = setSkyColour,
  70.     getSkyColor = getSkyColour,
  71.  
  72.     setTile = function(self, x, y, tile)
  73.         self.tiles[y][x] = tile
  74.     end,
  75.  
  76.     getTile = function(self, x, y)
  77.         assert(x and y)
  78.  
  79.         x = math.floor(x)
  80.         y = math.floor(y)
  81.  
  82.         if x > self.width or y > self.height or x < 1 or y < 1 then
  83.             return 0
  84.         end
  85.  
  86.         return self.tiles[y][x]
  87.     end,
  88.  
  89.     clearTiles = function(self)
  90.         self.tiles = {}
  91.     end,
  92.  
  93.     loadFromFile = function(self, file)
  94.         local f = assert(fs.open(file, "r"), "failed to open file " .. file)
  95.  
  96.         self:clearTiles()
  97.  
  98.         local line = f.readLine()
  99.         local y = 1
  100.         while line ~= nil do
  101.             --# Create a new row at the current position.
  102.             self.tiles[y] = {}
  103.  
  104.             for x=1,#line do --# For every character in the line...
  105.                 local c = line:sub(x, x) --# Gets the current character.
  106.                 local i = BASE_16:find(c) --# Convert hex to decimal.
  107.                 if i ~= nil then
  108.                     self.tiles[y][x] = i --# Insert the tile into the row.
  109.                 else
  110.                     self.tiles[y][x] = 0 --# If i is nil, there is no tile.
  111.                 end
  112.             end
  113.  
  114.             self.width = #line
  115.  
  116.             y = y + 1
  117.             line = f.readLine()
  118.         end
  119.  
  120.         self.height = y - 1
  121.  
  122.         f.close()
  123.     end,
  124.  
  125.     getWidth = function(self)
  126.         return self.width
  127.     end,
  128.  
  129.     getHeight = function(self)
  130.         return self.height
  131.     end,
  132.  
  133.     getSize = function(self)
  134.         return self.width,self.height
  135.     end,
  136.  
  137.     --# Casts a ray from a specified position at an angle with a specified maximum length.
  138.     --# Returns the tile it hit aswell as the distance the ray travelled.
  139.     castRay = function(self, angle, length)
  140.         local x = self.cameraX
  141.         local y = self.cameraY
  142.  
  143.         --# Compute the target position so that we can interpolate to it.
  144.         local targetX = x + math.cos(math.rad(angle)) * length
  145.         local targetY = y + math.sin(math.rad(angle)) * length
  146.  
  147.         --# Calculate the distance between the start and target,
  148.         --# so that we can get the increase for the for loop.
  149.         local dist = hypot(targetX - x, targetY - y)
  150.         local increase = 1/dist
  151.  
  152.         for t=0,1,increase do
  153.             --# Get the next position on the line between the start position and target position.
  154.             local nextX = lerp(x, targetX, t)
  155.             local nextY = lerp(y, targetY, t)
  156.  
  157.             local nextTile = self:getTile(nextX, nextY)
  158.  
  159.             if nextTile ~= nil and nextTile > 0 then --# We've hit a tile.
  160.                 return nextTile, hypot(nextX - self.cameraX, nextY - self.cameraY)
  161.             end
  162.         end
  163.  
  164.         return 16, 0
  165.     end,
  166.  
  167.     --# For debugging.
  168.     draw2D = function(self, surface)
  169.         local surf = surface or term
  170.         local w,h = surf.getSize()
  171.  
  172.         for y=1,self.height do
  173.             for x=1,self.width do
  174.                 local i = self:getTile(x, y)
  175.  
  176.                 if i > 0 then
  177.                     local col = 2 ^ (i - 1)
  178.                     surf.setCursorPos(x, y)
  179.                     surf.setBackgroundColour(col)
  180.                     surf.write(" ")
  181.                 end
  182.             end
  183.         end
  184.  
  185.         for i=1,w do
  186.             local t = i/w
  187.             local angle = lerp(self.cameraAngle - self.cameraFov / 2, self.cameraAngle + self.cameraFov / 2, t)
  188.             surf.setCursorPos(self.cameraX, self.cameraY)
  189.             surf.setBackgroundColour(colours.blue)
  190.             surf.write(" ")
  191.             surf.setCursorPos(self.cameraX + math.floor(math.cos(math.rad(self.cameraAngle)) + 0.5), self.cameraY + math.floor(math.sin(math.rad(self.cameraAngle)) + 0.5))
  192.             surf.write(" ")
  193.         end
  194.     end,
  195.  
  196.     draw3D = function(self, surface)
  197.         if self.cameraAngle > 360 then
  198.             self.cameraAngle = self.cameraAngle - 360
  199.         elseif self.cameraAngle < 0 then
  200.             self.cameraAngle = self.cameraAngle + 360
  201.         end
  202.  
  203.         local surf = surface or term
  204.         local w,h = surf.getSize()
  205.         local drawDist = 64
  206.  
  207.         surf.setBackgroundColour(self.skyColour)
  208.         surf.clear()
  209.  
  210.         for i=1,w do
  211.             local t = i/w
  212.             local angle = lerp(self.cameraAngle - self.cameraFov / 2, self.cameraAngle + self.cameraFov / 2, t)
  213.  
  214.             local tile, dist = self:castRay(angle, drawDist)
  215.             local top, height = self:project(self.wallHeight, h, angle, dist)
  216.  
  217.             for j=math.ceil(top),math.ceil(top+height) do
  218.                 surf.setCursorPos(i, j)
  219.                 surf.setBackgroundColour(2 ^ (tile - 1))
  220.                 surf.write(" ")
  221.                 surf.setCursorPos(1, 1)
  222.             end
  223.         end
  224.     end,
  225.  
  226.     project = function(self, height, surfHeight, angle, dist)
  227.         local depth = dist
  228.         local wallHeight = surfHeight * height / depth
  229.         local bottom = surfHeight / 2 * (1 + 1 / depth)
  230.         return bottom - wallHeight, wallHeight
  231.     end
  232. }
  233.  
  234. Raycaster.__index = Raycaster
  235.  
  236. setmetatable(Raycaster, {
  237.     __call = function(cls, ...)
  238.         local self = setmetatable({}, cls)
  239.         cls.new(self, ...)
  240.         return self
  241.     end
  242. })
  243.  
  244. function newRaycaster(...)
  245.     return Raycaster(...)
  246. end
Advertisement
Add Comment
Please, Sign In to add comment