Message
| I can't get the 3D mapper to work right now (mainly because of the horrible way I was getting the mapping data into it), but this snippet (which you can run in the Immediate window) actually draws one 3D scene which a constant at the start of it. To try it out you need the texture files, which I don't want to supply because I'm not sure of the copyright. But it shows the general idea.
-- 3D mapper test
-- Written by Nick Gammon
-- Date: August 2008
-- test map
map = [==[
--- ---
|{+} {!} {+}|
------- ---|
+{+} {+} {+} {+>|
------- ---
[?] <!> [?]
---
[?] <#> < . [?]
---
[?] < > [?]
------- -------
- - -!- < > -*- - -
------- -------
|. .|
]==]
local mapWidth = 80
local mapHeight = 80
local texWidth = 64
local texHeight = 64
posX = 35; posY = 35; --x and y start position
dirX = 0; dirY = -1; --initial direction vector
planeX = -0.7853; planeY = 0; --the 2d raycaster version of camera plane
HEIGHT_MULTIPLIER = 4
dir = "/Program Files/mushclient/"
require "tprint"
require "getlines"
-- make local for speed
local floor = math.floor
local ceil = math.ceil
local abs = math.abs
local sqrt = math.sqrt
local cos = math.cos
local sin = math.sin
local WindowLine = WindowLine
local function int (x)
if x >= 0 then
return floor (x)
end -- if positive
return ceil (x)
end -- function int
function setup_map_window ()
WINDOW_HEIGHT = 300
WINDOW_WIDTH = 400 -- 320
win = "3d"
wins = "3dshadow"
-- how many pixels per tile
tilewidth = WINDOW_WIDTH / mapWidth
tileheight = WINDOW_HEIGHT / mapHeight
WindowCreate (win,
0, -- left
0, -- top
WINDOW_WIDTH, -- width
WINDOW_HEIGHT, -- height
8, -- mode
0, -- flags
0x383838)
WindowCreate (wins,
0, -- left
0, -- top
WINDOW_WIDTH, -- width
WINDOW_HEIGHT, -- height
8, -- mode
0, -- flags
0xFFFFFF)
WindowShow (win, true)
local pencolour = 0x555555
local centre = WINDOW_HEIGHT / 2
-- sky
WindowGradient (win, 0, 0, 0, centre,
0xFFBF00,
ColourNameToRGB ("darkblue"),
2) -- top to bottom
-- ground
WindowGradient (win, 0, centre, 0, 0,
0x00091C,
ColourNameToRGB ("lightsalmon"),
2) -- top to bottom
-- noise
WindowFilter (win, 0, 0, 0, 0, 2, 15)
WindowRectOp (win, 1, -- frame rectangle
0, 0,
0, 0,
0x00002F)
images = {
"eagle", -- 1
"redbrick", -- 2
"purplestone", -- 3
"greystone", -- 4
"bluestone", -- 5
"mossy", -- 6
"wood", -- 7
"colorstone", -- 8
}
check (WindowLoadImage (win, "eagle", dir .. "eagle.bmp"))
check (WindowLoadImage (win, "redbrick", dir .. "redbrick.bmp"))
check (WindowLoadImage (win, "purplestone", dir .. "purplestone.bmp"))
check (WindowLoadImage (win, "greystone", dir .. "greystone.bmp"))
check (WindowLoadImage (win, "bluestone", dir .. "bluestone.bmp"))
check (WindowLoadImage (win, "mossy", dir .. "mossy.bmp"))
check (WindowLoadImage (win, "wood", dir .. "wood.bmp"))
check (WindowLoadImage (win, "colorstone", dir .. "colorstone.bmp"))
end -- setup_map_window
local colour_map = {
[1] = ColourNameToRGB ("red"),
[2] = ColourNameToRGB ("green"),
[3] = ColourNameToRGB ("blue"),
[4] = ColourNameToRGB ("white"),
} -- end colour_map
local function hwall (wx, wy, which)
for i = wx, wx + 9 do
worldMap [i] [wy] = which
end -- for
end -- hwall
local function vwall (wx, wy, which)
for i = wy, wy + 9 do
worldMap [wx] [i] = which
end -- for
end -- hwall
function convert_map ()
-- empty table
maprooms = {}
for x = 1, 8 do
maprooms [x] = {}
end -- for each x
mapline = {}
x = 1
for line in getlines (map) do
mapline [x] = line
x = x + 1
end -- for
-- bleecccchh
for x = 1, 8 do
for y = 1, 8 do
local t = {}
maprooms [x] [y] = t
t.top = mapline [y * 2 - 1]:sub ((x - 1) * 4 + 1, (x - 1) * 4 + 5)
t.middle = mapline [y * 2 ]:sub ((x - 1) * 4 + 1, (x - 1) * 4 + 5)
t.bottom = mapline [y * 2 + 1]:sub ((x - 1) * 4 + 1, (x - 1) * 4 + 5)
end -- each y
end -- each x
worldMap = {}
for x = 1, mapWidth do
local t = {}
worldMap [x] = t
for y = 1, mapHeight do
t [y] = 0 -- nothing there yet
end -- for
end -- for
for x = 1, 8 do
for y = 1, 8 do
t = maprooms [x] [y]
local wx = (x - 1) * 10 + 1
local wy = (y - 1) * 10 + 1
if t.top:sub (2, 3) == "--" then
hwall (wx, wy, 4)
end -- if
if t.bottom:sub (2, 3) == "--" then
hwall (wx, wy + 9, 4)
end -- if
if t.top:sub (2, 3) == "-+" then
hwall (wx, wy, 2)
end -- if
if t.bottom:sub (2, 3) == "-+" then
hwall (wx, wy, 2)
end -- if
if t.middle:sub (-1) == "|" then
vwall (wx + 9, wy, 4)
end -- if
if t.middle:sub (1, 1) == "|" then
vwall (wx, wy, 4)
end -- if
if t.middle == " [?] " then
vwall (wx, wy, 7)
vwall (wx + 9, wy, 7)
end -- if
end -- each y
end -- each x
end -- convert_map
texture = {}
for x = 1, 64 do
texture [x] = {}
for y = 1, 64 do
local c = bit.xor (x, y)
texture [x] [y] = c + c * 0x000200 + x * 0x010000
end -- for
end -- for
function Display_Map ()
convert_map ()
setup_map_window ()
local w = WINDOW_WIDTH
local h = WINDOW_HEIGHT
for x = 0, w - 1 do
--calculate ray position and direction
local cameraX = 2 * x / w - 1 --x-coordinate in camera space
local rayPosX = posX
local rayPosY = posY
local rayDirX = dirX + planeX * cameraX
local rayDirY = dirY + planeY * cameraX
--which box of the map we're in
local mapX = int (rayPosX)
local mapY = int (rayPosY)
--length of ray from current position to next x or y-side
local sideDistX
local sideDistY
--length of ray from one x or y-side to next x or y-side
local deltaDistX = sqrt (1 + (rayDirY * rayDirY) / (rayDirX * rayDirX))
local deltaDistY = sqrt (1 + (rayDirX * rayDirX) / (rayDirY * rayDirY))
local perpWallDist
--what direction to step in x or y-direction (either +1 or -1)
local stepX
local stepY
local hit = false --was there a wall hit?
local YsideHit --was a NS or a EW wall hit?
-- calculate step and initial sideDist
if rayDirX < 0 then
stepX = -1
sideDistX = (rayPosX - mapX) * deltaDistX
else
stepX = 1
sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX
end -- if
if rayDirY < 0 then
stepY = -1
sideDistY = (rayPosY - mapY) * deltaDistY
else
stepY = 1
sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY
end -- if
--perform DDA
while not hit do
--jump to next map square, OR in x-direction, OR in y-direction
if sideDistX < sideDistY then
sideDistX = sideDistX + deltaDistX
mapX = int (mapX + stepX)
YsideHit = false
else
sideDistY = sideDistY + deltaDistY
mapY = int (mapY + stepY)
YsideHit = true
end -- if
if mapX < 0 or
mapY < 0 or
mapX > mapWidth or
mapY > mapHeight then
break
end -- outside map
if worldMap[mapX] == nil then
break
end -- if off map
if worldMap[mapX] [mapY] == nil then
break
end -- if off map
--Check if ray has hit a wall
if worldMap[mapX][mapY] > 0 then
hit = true
end -- if hit
end -- while
local wallX --where exactly the wall was hit
local drawEnd
--calculate value of wallX
if YsideHit then
wallX = rayPosX + ((mapY - rayPosY + (1 - stepY) / 2) / rayDirY) * rayDirX
else
wallX = rayPosY + ((mapX - rayPosX + (1 - stepX) / 2) / rayDirX) * rayDirY
end -- if
-- Calculate distance projected on camera direction (oblique distance will give fisheye effect!)
if not YsideHit then
perpWallDist = abs ((mapX - rayPosX + (1 - stepX) / 2) / rayDirX)
else
perpWallDist = abs ((mapY - rayPosY + (1 - stepY) / 2) / rayDirY)
end -- if
--Calculate height of line to draw on screen
local lineHeight = int (abs (h / perpWallDist) * HEIGHT_MULTIPLIER)
--calculate lowest and highest pixel to fill in current stripe
local drawStart = -lineHeight / 2 + h / 2
if drawStart < 0 then
drawStart = 0
end -- if
drawEnd = lineHeight / 2 + h / 2
if drawEnd >= h then
drawEnd = h - 1
end -- if
local adjwallX = wallX / 10
adjwallX = adjwallX - floor((adjwallX))
if hit then
--texturing calculations
local texNum = worldMap [mapX][mapY]
--x coordinate on the texture
local texX = int (adjwallX * texWidth)
if (not YsideHit) and (rayDirX > 0) then
texX = texWidth - texX - 1
end -- if
if YsideHit and (rayDirY < 0) then
texX = texWidth - texX - 1
end -- if
check (WindowDrawImage(win, images [texNum], x + 1, drawStart , x + 2, drawEnd,
2,
texX, 0, texX + 1, 0))
check (WindowFilter (win, x + 1, drawStart , x + 2, drawEnd, 7, -perpWallDist * 4)) -- darken
if x > w / 2 then
check (WindowFilter (win, x + 1, drawStart , x + 2, drawEnd, 9, 1.4)) -- gamma (darken)
else
check (WindowFilter (win, x + 1, drawStart , x + 2, drawEnd, 9, .8)) -- gamma (lighten)
end -- if on RH side
end -- of hit on wall
--FLOOR CASTING
local floorXWall, floorYWall -- x, y position of the floor texel at the bottom of the wall
--4 different wall directions possible
if not YsideHit and (rayDirX > 0) then
floorXWall = mapX
floorYWall = mapY + wallX
elseif not YsideHit and (rayDirX < 0) then
floorXWall = mapX + 1.0
floorYWall = mapY + wallX
elseif YsideHit and (rayDirY > 0) then
floorXWall = mapX + wallX
floorYWall = mapY
else
floorXWall = mapX + wallX
floorYWall = mapY + 1.0
end -- if
local distWall, currentDist
distWall = perpWallDist
if drawEnd < 0 then
drawEnd = h -- becomes < 0 when the integer overflows
end -- if
--draw the floor from drawEnd to the bottom of the screen
for y = int (drawEnd + 1), h do
currentDist = h / (2 * y - h) -- you could make a small lookup table for this instead
--floor
WindowSetPixel (win, x, y, 0x0000FF / currentDist)
-- ceiling (symmetrical!)
WindowSetPixel (win, x, h- y, int (256 / currentDist) * 256)
end -- for loop
-- shadow
if x > w / 2 then
WindowLine(wins, x + 1, drawEnd , x - abs (drawStart - drawEnd), drawEnd, 0x777777, 0, 1)
end -- if
end -- for each x
WindowFilter(wins, 0, 0, 0, 0, 3, 0)
WindowImageFromWindow (win, "shadows", wins)
WindowBlendImage (win, "shadows", 4, 0, 0, 0, 6, 1)
end -- Display_Map
Display_Map ()
That's under 500 lines of Lua code to render a 3D window.
Below is the output as rendered by the above code.
That took 0.17 seconds to do the Display_Map part. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|