Skip to content

Commit

Permalink
Some optimisations to ray casting
Browse files Browse the repository at this point in the history
  • Loading branch information
danielholmes committed Feb 23, 2020
1 parent 8e633cc commit 28d8dfc
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 96 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ stack test --pedantic --file-watch
## TODO

- use data.array for worldmap instead - better performance accessing by index
- use sin, cos, tan lookup tables
- use tan lookup tables
- have press m to toggle map for debug
- https://github.com/bkaradzic/bgfx looks good for rendering
- OpenGL renderer - https://hackage.haskell.org/package/OpenGL
- See https://github.com/jxv/sdl2-fps
Expand Down
5 changes: 4 additions & 1 deletion src/Wolf3D/Display.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Wolf3D.Runner
import Wolf3D.SDLUtils
import Wolf3D.Display.Utils
import Wolf3D.Display.Hud
import Wolf3D.Display.Ray
import Wolf3D.Animation
import qualified SDL
import Data.StateVar (($=))
Expand All @@ -31,6 +32,7 @@ import qualified Data.Map as M
import Foreign.C.Types (CInt)
import GHC.Word (Word8)
import Wolf3D.Display.Data
import Debug.Trace


-- deprecated, not sure what it is actually
Expand Down Expand Up @@ -133,7 +135,8 @@ pixelWallHit :: World Wolf3DSimEntity -> CInt -> Maybe (WallHit, Double)
pixelWallHit w i = fmap (\h -> (h, perpendicularDistance rayRotation h)) (castRayToClosestWall w rotatedRay)
where
hero = worldHero w
hRay = heroLookRay hero
hit = castRayToWalls (worldWallMap w) (position hero) (snappedRotation hero)
hRay = traceShow ("hit", hit) (heroLookRay hero)
ratio = fromIntegral (actionWidth - i) / fromIntegral actionWidth
rayRotation = pi / 3 * (ratio - 0.5)
rotatedRay = rotateRay hRay rayRotation
Expand Down
143 changes: 82 additions & 61 deletions src/Wolf3D/Display/Ray.hs
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
module Wolf3D.Display.Ray (
castRay,
castRayToWalls,
castRaysToWalls,
HitDirection (Horizontal, Vertical),
Hit (Hit, material, intercept, direction)
WallRayHit (WallRayHit, material, tilePosition, intercept, direction)
) where

import Data.Vector
import Wolf3D.Engine
import Wolf3D.Sim
import Wolf3D.Geom
import Data.Bits
import Debug.Trace
import Data.Array
import Wolf3D.Display.Data

data HitDirection = Horizontal | Vertical
deriving (Show, Eq)

data Hit = Hit {material :: WallMaterial, direction :: HitDirection, intercept :: (Int, Int)}
data WallRayHit = WallRayHit {material :: WallMaterial
, direction :: HitDirection
, tilePosition :: Int
, intercept :: (Int, Int)}
deriving (Eq, Show)

-- Taken from original source, don't know exact meaning yet
focalLength :: Double
focalLength = 0x5700

--sinTable :: [Double]
--sinTable = map (\a -> (fromIntegral tileGlobalSize) * sin (a * deg2Rad)) [0..(360 - 1)]
--
--cosTable :: [Double]
--cosTable = map (\a -> (fromIntegral tileGlobalSize) * cos (a * deg2Rad)) [0..(360 - 1)]
anglesList :: [Int]
anglesList = [0..(angles - 1)]

sinTable :: Array Int Double
sinTable = array (0, angles - 1) [(i, sin (fromIntegral i * deg2Rad)) | i <- anglesList]

cosTable :: Array Int Double
cosTable = array (0, angles - 1) [(i, cos (fromIntegral i * deg2Rad)) | i <- anglesList]

-- TODO: fineangle tan

-- TODO: checks for double hor/ver remove need for separate hor/ver data. Trial it
data RayData = DiagonalRayData {horInterceptX :: Int
, horInterceptYTile :: Int
, verInterceptY :: Int
data RayData = DiagonalRayData {horInterceptYTile :: Int
, verInterceptXTile :: Int
, xTileStep :: Int
, yTileStep :: Int
Expand All @@ -46,9 +54,23 @@ data RayData = DiagonalRayData {horInterceptX :: Int
, tileStep :: Int}
deriving (Show)

castRay :: WallMap -> Vector2 -> SnappedRotation -> Maybe Hit
castRay [] _ _ = Nothing
castRay wm pos viewAngle = nextRayCheck wm focal rd
fieldOfView :: Double
fieldOfView = pi / 3

-- Note, needs to be fine angles
viewAngleOffsets :: [Int]
viewAngleOffsets = map (\i -> round (fromIntegral i * angleDiff)) [0..(actionWidth - 1)]
where
angleDiff = fieldOfView / fromIntegral actionWidth

castRaysToWalls :: WallMap -> Vector2 -> SnappedRotation -> [WallRayHit]
castRaysToWalls wm pos midAngle = map (\a -> castRayToWalls wm pos a) rayAngles
where
angleStart = round (fromIntegral midAngle - fieldOfView / 2)
rayAngles = map (\i -> angleStart + i) viewAngleOffsets

castRayToWalls :: WallMap -> Vector2 -> SnappedRotation -> WallRayHit
castRayToWalls wm pos viewAngle = nextRayCheck wm focal rd
where (focal, rd) = createRayData pos viewAngle

createRayData :: Vector2 -> SnappedRotation -> ((Int, Int), RayData)
Expand All @@ -63,18 +85,16 @@ createRayData (Vector2 x y) viewAngle
, tileStep=yTileStep1})
| viewAngle > 0 && viewAngle < 90 = let
tanTheta = tan angleRad
-- TODO: Shifting
aX = focalXI + round (fromIntegral (focalYI - focalTileY * tileGlobalSize) * tanTheta)
-- TODO: Use focalXI shift instead
-- aX = focalXI + round (fromIntegral (focalYI - focalTileY * tileGlobalSize) * tanTheta)
aX = focalXI + yPartial
-- TODO: Use focalXI shift instead, or partial?
bYX = fromIntegral (focalXI - (focalTileX * tileGlobalSize))
bY = focalYI - round (bYX / tanTheta)
in
(focal
, DiagonalRayData {horNextIntercept=(aX, (horInterceptYTile1 + 1) * tileGlobalSize)
, verNextIntercept=((focalTileX + xTileStep1) * tileGlobalSize, bY)
, horInterceptX=aX
, horInterceptYTile=horInterceptYTile1
, verInterceptY=bY
, verInterceptXTile=focalTileX + xTileStep1
, xTileStep=xTileStep1
, yTileStep=yTileStep1
Expand All @@ -91,9 +111,7 @@ createRayData (Vector2 x y) viewAngle
(focal
, DiagonalRayData {horNextIntercept=(aX, (horInterceptYTile1 + 1) * tileGlobalSize)
, verNextIntercept=(focalTileX * tileGlobalSize, bY)
, horInterceptX=aX
, horInterceptYTile=horInterceptYTile1
, verInterceptY=bY
, verInterceptXTile=focalTileX + xTileStep1
, xTileStep=xTileStep1
, yTileStep=yTileStep1
Expand All @@ -109,9 +127,7 @@ createRayData (Vector2 x y) viewAngle
(focal
, DiagonalRayData {horNextIntercept=(aX, horInterceptYTile1 * tileGlobalSize)
, verNextIntercept=(focalTileX * tileGlobalSize, bY)
, horInterceptX=aX
, horInterceptYTile=horInterceptYTile1
, verInterceptY=bY
, verInterceptXTile=focalTileX + xTileStep1
, xTileStep=xTileStep1
, yTileStep=yTileStep1
Expand All @@ -125,9 +141,7 @@ createRayData (Vector2 x y) viewAngle
(focal
, DiagonalRayData {horNextIntercept=(aX, horInterceptYTile1 * tileGlobalSize)
, verNextIntercept=((focalTileX + xTileStep1) * tileGlobalSize, bY)
, horInterceptX=aX
, horInterceptYTile=horInterceptYTile1
, verInterceptY=bY
, verInterceptXTile=focalTileX + xTileStep1
, xTileStep=xTileStep1
, yTileStep=yTileStep1
Expand All @@ -136,8 +150,8 @@ createRayData (Vector2 x y) viewAngle
| otherwise = error "Angle must be >= 0 < 360"
where
angleRad = fromIntegral viewAngle * deg2Rad
viewSin = sin angleRad -- skip a * tileGlobalSize, seems like sizes are correct w/o
viewCos = cos angleRad -- skip a * tileGlobalSize, seems like sizes are correct w/o
viewSin = sinTable!viewAngle -- skip a * tileGlobalSize, seems like sizes are correct w/o
viewCos = cosTable!viewAngle -- skip a * tileGlobalSize, seems like sizes are correct w/o
focalX = x - (focalLength * viewCos)
focalY = y + (focalLength * viewSin)
focalXI :: Int
Expand All @@ -149,8 +163,8 @@ createRayData (Vector2 x y) viewAngle
focalTileY = focalYI `shiftR` tileToGlobalShift
-- xPartialDown = focalXI .&. (tileGlobalSize - 1)
-- xPartialUp = tileGlobalSize - xPartialDown
-- yPartialDown = focalYI .&. (tileGlobalSize - 1)
-- yPartialUp = tileGlobalSize - yPartialDown
yPartialDown = focalYI .&. (tileGlobalSize - 1)
yPartialUp = tileGlobalSize - yPartialDown

-- This is for 0-89deg, 90-179
-- Taken directly from source, find a better way
Expand All @@ -163,77 +177,84 @@ createRayData (Vector2 x y) viewAngle
yTileStep1 = if is180To269 || is270To359 then 1 else -1 :: Int

-- xPartial = if is0To89 || is270To359 then xPartialUp else xPartialDown
-- yPartial = if is180To269 || is270To359 then yPartialUp else yPartialDown
yPartial = if is180To269 || is270To359 then yPartialUp else yPartialDown

horInterceptYTile1 = focalTileY + yTileStep1
verInterceptXTile1 = focalTileX + xTileStep1

nextRayCheck :: WallMap -> (Int, Int) -> RayData -> Maybe Hit
nextRayCheck wm p@(_, y) nextD@DiagonalRayData{ horNextIntercept=(_, hY)
, verNextIntercept=(vX, vY)}
| nextHorYStep < nextVerYStep = traceShow debug (horRayCheck wm nextD)
| otherwise = traceShow debug (verRayCheck wm nextD)
nextRayCheck :: WallMap -> (Int, Int) -> RayData -> WallRayHit
nextRayCheck wm (_, y) nextD@DiagonalRayData{ horNextIntercept=(_, hY)
, verNextIntercept=(_, vY)}
| nextHorYStep < nextVerYStep = horRayCheck wm nextD
| otherwise = verRayCheck wm nextD
where
nextHorYStep = abs (hY - y)
nextVerYStep = abs (vY - y)
debug = (" nextRayCheck", nextHorYStep, nextVerYStep, p, (vX, vY))
nextRayCheck wm _ d@(HorizontalRayData _ _ _) = verRayCheck wm d
nextRayCheck wm _ d@(VerticalRayData _ _ _) = horRayCheck wm d

verRayCheck :: WallMap -> RayData -> Maybe Hit
verRayCheck :: WallMap -> RayData -> WallRayHit
verRayCheck _ (VerticalRayData {}) = error "Shouldn't be called"
verRayCheck wm d@DiagonalRayData {verInterceptY=vIY
, verInterceptXTile=vIXTile
verRayCheck wm d@DiagonalRayData {verInterceptXTile=vIXTile
, xTileStep=xTileS
, yStep=dy
, verNextIntercept=currentIntercept@(x, y)} = case traceShow ("ver", currentIntercept, (vIXTile, vIYTile)) hitting of
Nothing -> traceShow (" nothing", currentIntercept, vInterceptNext, dy) (nextRayCheck wm currentIntercept nextD)
Just m -> traceShow " mat" (Just (Hit {material=m, direction=Vertical, intercept=currentIntercept}))
, verNextIntercept=currentIntercept@(x, y)} = case hitting of
Nothing -> nextRayCheck wm currentIntercept nextD
Just m -> WallRayHit {material=m
, tilePosition=y - vIYTile * tileGlobalSize
, direction=Vertical
, intercept=currentIntercept}
where
vIYTile = (vIY - 1) `shiftR` tileToGlobalShift
vIYTile = (y - 1) `shiftR` tileToGlobalShift
-- intXTile = if xTileS == -1 then vIXTile + 1 else vIXTile
hitting = wm!!vIXTile!!vIYTile
-- vIntercept = (intXTile * tileGlobalSize, vIY)

vInterceptNext = (x + xTileS * tileGlobalSize, y + dy)
nextD = d{verInterceptY=(vIY + dy)
, verInterceptXTile=(vIXTile + xTileS)
nextD = d{verInterceptXTile=(vIXTile + xTileS)
, verNextIntercept=vInterceptNext}

verRayCheck wm d@HorizontalRayData {interceptY=vIY
, interceptXTile=vIXTile
, tileStep=xTileS} = case traceShow ("verHH", vIXTile, vIYTile) hitting of
Nothing -> traceShow " nothing" (verRayCheck wm d{interceptXTile=(vIXTile + xTileS)})
Just m -> traceShow " mat" (Just (Hit {material=m, direction=Vertical, intercept=vIntercept}))
, tileStep=xTileS} = case hitting of
Nothing -> verRayCheck wm d{interceptXTile=(vIXTile + xTileS)}
Just m -> WallRayHit {material=m
, tilePosition=vIY - vIYTile * tileGlobalSize
, direction=Vertical
, intercept=vIntercept}
where
vIYTile = (vIY - 1) `shiftR` tileToGlobalShift
hitting = wm!!vIXTile!!vIYTile

vInterceptX = vIXTile * tileGlobalSize
vIntercept = (if xTileS == -1 then vInterceptX + tileGlobalSize else vInterceptX, vIY)

horRayCheck :: WallMap -> RayData -> Maybe Hit
horRayCheck :: WallMap -> RayData -> WallRayHit
horRayCheck _ (HorizontalRayData {}) = error "Shouldn't be called"
horRayCheck wm d@DiagonalRayData {horInterceptX=hIX
, horInterceptYTile=hIYTile
horRayCheck wm d@DiagonalRayData {horInterceptYTile=hIYTile
, yTileStep=dYTile
, xStep=dX
, horNextIntercept=currentIntercept@(x, y)} = case traceShow ("hor", currentIntercept, (hIXTile, hIYTile)) hitting of
Nothing -> traceShow " nothing" (nextRayCheck wm currentIntercept nextD)
Just m -> traceShow " mat" (Just (Hit {material=m, direction=Horizontal, intercept=currentIntercept}))
, horNextIntercept=currentIntercept@(x, y)} = case hitting of
Nothing -> nextRayCheck wm currentIntercept nextD
Just m -> WallRayHit {material=m
, tilePosition=x - hIXTile * tileGlobalSize
, direction=Horizontal
, intercept=currentIntercept}
where
hIXTile = (hIX - 1) `shiftR` tileToGlobalShift
hIXTile = (x - 1) `shiftR` tileToGlobalShift
hitting = wm!!hIXTile!!hIYTile

hInterceptNext = (x + dX, y + dYTile * tileGlobalSize)
nextD = d{horInterceptX=(hIX + dX)
, horInterceptYTile=(hIYTile + dYTile)
nextD = d{horInterceptYTile=(hIYTile + dYTile)
, horNextIntercept=hInterceptNext}
horRayCheck wm d@VerticalRayData {interceptX=hIX
, interceptYTile=hIYTile
, tileStep=dYTile} = case traceShow ("horVV", hIXTile, hIYTile) hitting of
Nothing -> traceShow " nothing" (horRayCheck wm d{interceptYTile=(hIYTile + dYTile)})
Just m -> traceShow " mat" (Just (Hit {material=m, direction=Horizontal, intercept=hIntercept}))
, tileStep=dYTile} = case hitting of
Nothing -> horRayCheck wm d{interceptYTile=(hIYTile + dYTile)}
Just m -> WallRayHit {material=m
, tilePosition=hIX - hIXTile * tileGlobalSize
, direction=Horizontal
, intercept=hIntercept}
where
hIXTile = (hIX - 1) `shiftR` tileToGlobalShift
hitting = wm!!hIXTile!!hIYTile
Expand Down
1 change: 1 addition & 0 deletions src/Wolf3D/Sim.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Wolf3D.Sim (
Weapon (Pistol),
lastTimeWeaponUsed,

angles,
createHero,
createHeroFromTilePosition,
heroLookRay,
Expand Down
4 changes: 4 additions & 0 deletions stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ extra-deps:
- "sdl2-ttf-2.1.0"
- "stopwatch-0.1.0.6"
- "clock-0.8"
- "array-0.5.4.0"
# Fixes a bug recently showed up in compiling. These recommended by stack
- binary-0.8.8.0
- directory-1.3.6.0
Expand All @@ -65,6 +66,9 @@ extra-deps:
- hspec-discover-2.7.1
- HUnit-1.6.0.0
- splitmix-0.0.3
- bytestring-0.10.10.0
- deepseq-1.4.4.0
- pretty-1.1.3.6

# Override default flag values for local packages and extra-deps
flags: {}
Expand Down
28 changes: 28 additions & 0 deletions stack.yaml.lock
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ packages:
sha256: d9ac8ce6a19812fe74cb1e74228e6e624eda19021ff4d12e24611f88abacd38a
original:
hackage: clock-0.8
- completed:
hackage: array-0.5.4.0@sha256:2b2425e01d592c0e66594c008405ea4c6f6e4d929daeddabbd81be9bb49ebffa,1588
pantry-tree:
size: 1135
sha256: e17dfafde2f226674cffd56b1f445c332b6e5855860f79dedd9de0b6916e7ff0
original:
hackage: array-0.5.4.0
- completed:
hackage: binary-0.8.8.0@sha256:e9387a7ef2b34c6a23b09664c306e37cc01ae2cb4e4511a1c96ffb14008c24b0,6262
pantry-tree:
Expand Down Expand Up @@ -172,6 +179,27 @@ packages:
sha256: 32f9dc84cb1845c5020bc9cb5c5c56e890d54d16b0cbf9c788825abb45165499
original:
hackage: splitmix-0.0.3
- completed:
hackage: bytestring-0.10.10.0@sha256:06b2e84f1bc9ab71a162c0ca9e88358dd6bbe5cb7fdda2d6d34b6863c367ec95,8944
pantry-tree:
size: 2788
sha256: 0c8345618e3d7440989d5001ff57d01896548195338bf7a70d8453710a43418b
original:
hackage: bytestring-0.10.10.0
- completed:
hackage: deepseq-1.4.4.0@sha256:76902b99ee97fc059311ddb0df9488c54969adf0a212efa39705981761f06b55,2918
pantry-tree:
size: 385
sha256: edf7ecb1e7195bf2c1b595f462343b8a237351acf73e4d0e66e005fc5baa62f4
original:
hackage: deepseq-1.4.4.0
- completed:
hackage: pretty-1.1.3.6@sha256:e16ffc733e816cfc09e99cc7f2398805f1f4c872d238a6b81668b5527284b382,2482
pantry-tree:
size: 1308
sha256: 9f62289797600d7121862309c306a3b495f872dccfe3d5c4bdeaf85d5ad12dd7
original:
hackage: pretty-1.1.3.6
snapshots:
- completed:
size: 535260
Expand Down
Loading

0 comments on commit 28d8dfc

Please sign in to comment.