Re: Textured Raycaster [Multiple Levels! Door Covers! Jumping!]
Posted: Thu Apr 06, 2017 5:41 am
Hey, Davidobot, if I could change the subject back to raycasting for a bit, I'd appreciate if you could help me solve a few problems I'm having with my own raycaster.
Now the main problem is since my code is much different from yours, things are done differently to get the same outcome. You seem to have solved a few of my problems so maybe you can help me solve them over here. We'll start with a simple one I guess...
I'm having trouble trying to get sprites to sort properly with the wall strips. Obviously their "distance" from the camera is calculated differently. I tried looking at your code but you calculate both your wall segment and sprite distances differently from mine which are calculated completely differently from each other. Here's the problem I'm having:
As you see, it seems that the sprite distance isn't properly "corrected". I've been ripping my hair out trying to figure out a good way to do it. Of course it only shows its ugly head when the sprite is next to a wall.
Here's my code. It's a dang mess I know. Some stuff is commented out because I just pulled it from my old project and haven't gotten around to implementing some stuff like doors yet.
First up, the raycasting function:
Which calls this function to actually create the strip itself: (Note I have to invert the "layer" which is the equivalent to the "z" value since I use my own drawPool library which sorts differently from the original. Just ignore it.)
And this code determines where sprites should render. This is probably where the problem is: (Note I use camera instead of player.)
Before you ask, I've tried uncommenting each of those last two commented lines and neither of them fix it, instead making it worse. I've also tried uncommenting that other commented dist line.
Also note RENDERWIDTH/HEIGHT are the dimensions of my rendered screen since I am rendering to a lower resolution than native resolution for reasons™.
Any help would make me so happy. I've tried everything except completely rip your code and put it in my version. I can probably provide the actual project itself if I need to.
Now the main problem is since my code is much different from yours, things are done differently to get the same outcome. You seem to have solved a few of my problems so maybe you can help me solve them over here. We'll start with a simple one I guess...
I'm having trouble trying to get sprites to sort properly with the wall strips. Obviously their "distance" from the camera is calculated differently. I tried looking at your code but you calculate both your wall segment and sprite distances differently from mine which are calculated completely differently from each other. Here's the problem I'm having:
As you see, it seems that the sprite distance isn't properly "corrected". I've been ripping my hair out trying to figure out a good way to do it. Of course it only shows its ugly head when the sprite is next to a wall.
Here's my code. It's a dang mess I know. Some stuff is commented out because I just pulled it from my old project and haven't gotten around to implementing some stuff like doors yet.
First up, the raycasting function:
Code: Select all
function castSingleRay(rayAngle, stripIdx, checkOnly)
-- first make sure the angle is between 0 and 360 degrees
rayAngle = rayAngle % twoPI
-- if (rayAngle < 0) then rayAngle = rayAngle + twoPI end
-- moving right/left? up/down? Determined by which quadrant the angle is in.
local right = (rayAngle > twoPI * 0.75) or (rayAngle < twoPI * 0.25)
local up = (rayAngle < 0) or (rayAngle > pi)
-- only do these once
local angleSin = math.sin(rayAngle)
local angleCos = math.cos(rayAngle)
local vHit = false
local dist = 0 -- the distance to the block we hit
local xHit = 0 -- the x and y coord of where the ray hit the block
local yHit = 0
local textureX -- the x-coord on the texture of the block, ie. what part of the texture are we going to render
local wallX -- the (x,y) map coords of the block
local wallY
-- first check against the vertical map/wall lines
-- we do this by moving to the right or left edge of the block we're standing in
-- and then moving in 1 map unit steps horizontally. The amount we have to move vertically
-- is determined by the slope of the ray, which is simply defined as sin(angle) / cos(angle).
local tileNum = nil
local tileNum_u = nil
local hitDoor = false
local slope = angleSin / angleCos -- the slope of the straight line made by the ray
-- we move either 1 map unit to the left or right
local dX
if right then dX = 1 else dX=-1 end
local dY = dX * slope -- how much to move up or down
-- starting horizontal position, at one of the edges of the current map block
local x
if right then x = math.ceil(camera.x) else x = math.floor(camera.x) end
-- starting vertical position. We add the small horizontal step we just made, multiplied by the slope.
local y = camera.y + (x - camera.x) * slope
local floorTileName
while (x >= 0 and x < mapWidth and y >= 0 and y < mapHeight) do
local wallX
if right then wallX = math.floor(x) else wallX = math.floor(x -1) end
local wallY = math.floor(y)
local inside = wallX > 0 and wallX <= mapWidth and wallY > 0 and wallY <= mapHeight
floorTileName = "fb" .. wallX+1 .. "-" .. wallY+1
if not floorBoxList[floorTileName] then
floorBoxList[floorTileName] = { wallX+1, wallY+1 }
end
if inside then
-- if doorMap[wallX+1][wallY+1].kind ~= "" and doorMap[wallX+1][wallY+1].orientation == "v" then
-- visibleDoors[doorMap[wallX+1][wallY+1].id] = { active = true, orientation = "v" }
-- end
end
-- is this point inside a wall block?
local k = 1+(math.floor(wallY))*mapWidth+math.floor(wallX)
if map[k] > 0 then
wallBoxList["wb" .. wallX+1 .. "-" .. wallY+1] = { wallX+1, wallY+1 }
floorBoxList["fb" .. wallX+1 .. "-" .. wallY+1] = nil
tileNum = map[k]
-- tileNum_u = map_upper[k]
local distX = x - camera.x
local distY = y - camera.y
dist = distX*distX + distY*distY -- the distance from the player to this point, squared.
-- where exactly are we on the wall? textureX is the x coordinate on the texture that we'll use when texturing the wall.
textureX = y % 1
-- if we're looking to the left side of the map, the texture should be reversed
if (not right) then textureX = 1 - textureX end
xHit = math.floor(x)
yHit = math.floor(y)
ix = x
iy = y
vHit = true
if right then
-- if doorMap[wallX][wallY+1].kind ~= "" and (xHit == wallX and yHit == wallY) and doorMap[wallX][wallY+1].orientation == "h" then
-- tileNum = 12
-- end
elseif not right then
-- if doorMap[wallX+2][wallY+1].kind ~= "" and (xHit == wallX+1 and yHit == wallY) and doorMap[wallX+2][wallY+1].orientation == "h" then
-- tileNum = 12
-- end
end
break
end
if inside then
-- if spriteMap[wallX][wallY] > -1 then visibleSprites[spriteMap[wallX][wallY]] = true end
end
x = x+dX
y = y+dY
end
-- now check against horizontal lines. It's basically the same, just "turned around".
-- the only difference here is that once we hit a map block,
-- we check if there we also found one in the earlier, vertical run. We'll know that if dist != 0.
-- If so, we only register this hit if this distance is smaller.
local slope = angleCos / angleSin
local dY = up and -1 or 1
local dX = dY * slope
local y
if up then y = math.floor(camera.y) else y = math.ceil(camera.y) end
local x = camera.x + (y - camera.y) * slope
while (x >= 0 and x < mapWidth and y >= 0 and y < mapHeight) do
local wallY
if up then wallY = math.floor(y - 1) else wallY = math.floor(y) end
local wallX = math.floor(x)
local inside = wallX > 0 and wallX <= mapWidth and wallY > 0 and wallY <= mapHeight
floorTileName = "fb" .. wallX+1 .. "-" .. wallY+1
if not floorBoxList[floorTileName] then
floorBoxList[floorTileName] = { wallX+1, wallY+1 }
end
if inside then
-- if doorMap[wallX+1][wallY+1].kind ~= "" and doorMap[wallX+1][wallY+1].orientation == "h" then
-- visibleDoors[doorMap[wallX+1][wallY+1].id] = { active = true, orientation = "h" }
-- end
end
local k=1+(math.floor(wallY))*mapWidth+math.floor(wallX)
if map[k] > 0 then
wallBoxList["wb" .. wallX+1 .. "-" .. wallY+1] = { wallX+1, wallY+1 }
floorBoxList[floorTileName] = nil
local distX = x - camera.x
local distY = y - camera.y
local blockDist = distX*distX + distY*distY
if (dist==0 or blockDist < dist) then
dist = blockDist
tileNum = map[k]
-- tileNum_u = map_upper[k]
xHit = math.floor(x)
yHit = math.floor(y)
ix = x
iy = y
vHit = false
textureX = x % 1
if (not up) then textureX = 1 - textureX end
end
-- if up then
-- if doorMap[wallX+1][wallY+2].kind ~= "" and (xHit == wallX and yHit == wallY+1) and doorMap[wallX][wallY+1].orientation == "v" then
-- tileNum = 12
-- end
-- elseif not up then
-- if doorMap[wallX+1][wallY].kind ~= "" and (xHit == wallX and yHit == wallY) and doorMap[wallX][wallY+1].orientation == "v" then
-- tileNum = 12
-- end
-- end
break
end
-- if inside then
-- if spriteMap[wallX][wallY] > -1 then visibleSprites[spriteMap[wallX][wallY]] = true end
-- end
x = x+dX
y = y+dY
end
if checkOnly then
return xHit, yHit, stripIdx, height, vHit, textureX, dist, tileNum
else
if (dist>0) then
dist = math.sqrt(dist)
dist = dist * math.cos(camera.rot - rayAngle)
local height = (viewDist / dist)-- * ceilingHeight
createStrip(stripIdx, height, vHit, textureX, dist, tileNum)
end
end
end
Code: Select all
function createStrip(stripIdx, height, vHit, textureX, dist, tileNum)
tileNum = tileNum or 4
darkness = 200 / dist
textureX = math.floor(textureX * tileSize)
local scale = ((RENDERHEIGHT+height)*0.5 - (RENDERHEIGHT-height)*0.5) / tileSize
-- z = -math.floor(dist*(10000))
local qu, ql = nil, nil
if tileNum > 0 then
ql = wallQuad[tileNum][textureX]
end
local lum = 1-(dist/fog_dist)
if lum < 0 then lum = 0 end
if vHit == false then lum = lum * .7 end
lum = lum * ambient_light
local c = {ambient_color[1] * lum, ambient_color[2] * lum, ambient_color[3] * lum}
local cz = false
if stripIdx == stateStack.mouseX and DEBUGMODE then
mouseStrip = dist
c = {255,0,0}
cz = true
end
if dist < fog_dist then
if DRAWWALLS then
fpsState.drawPool:push {
kind = "image",
image = wallImage,
quad = ql,
x = stripIdx,
y = (RENDERHEIGHT-height) * camera.eyeHeight,
sx = 1,
sy = scale,
layer = 100 - dist,
colorize = cz,
color = c,
ioy = wallSegmentOffset
}
else
local c = randColor[tileNum]
fpsState.drawPool:push {
kind = "rectangle",
fill = "fill",
x = stripIdx,
y = (RENDERHEIGHT-height) * camera.eyeHeight,
w = 1,
h = scale * tileSize,
layer = 100 - dist,
color = {c[1]*lum,c[2]*lum,c[3]*lum}
}
end
if DRAWDEBUGRAYLINES then fpsState.drawPool:push { kind = "rect", x = stripIdx, y = dist * 5, w = 1, h = 1, color = {255,255,255}, layer = 100000 } end
end
if furthestDist < dist then
furthestDist = dist
furthestStrip = stripIdx
furthestTop = math.floor((RENDERHEIGHT-height) * camera.eyeHeight)
furthestBottom = math.floor(furthestTop + scale * tileSize)
end
end
Code: Select all
function getScreen2DPosition(e)
local dx = e.x - camera.x
local dy = e.y - camera.y
-- local dist = math.sqrt(dx*dx + dy*dy)
local dist = math.sqrt((camera.x - e.x)^2 + (camera.y - e.y)^2)
local spriteAngle = math.atan2(dy, dx) - camera.rot
local size = viewDist / (math.cos(spriteAngle) * dist)
local x = math.tan(spriteAngle) * viewDist
local left = (RENDERWIDTH/2 + x)
local top = ((RENDERHEIGHT-size)*camera.eyeHeight)
local dbx = e.x - camera.x
local dby = e.y - camera.y
local blockDist = dbx*dbx + dby*dby
local z = -math.floor((dist-.5)*10000)
local lum = 1-(dist/fog_dist)
if lum < 0 then lum = 0 end
lum = lum * ambient_light
local scale = size / tileSize
-- dist = math.sqrt(dist)
-- dist = dist * math.cos(camera.rot - spriteAngle)
return left, top + (tileSize * scale), scale > 0, dist, scale, lum
end
Also note RENDERWIDTH/HEIGHT are the dimensions of my rendered screen since I am rendering to a lower resolution than native resolution for reasons™.
Any help would make me so happy. I've tried everything except completely rip your code and put it in my version. I can probably provide the actual project itself if I need to.