输出如下所示:

您应该只看到一侧是平坦的,连续的红色墙,另一侧是蓝色墙,另一侧是绿色,另一个是黄色(参见地图定义, testMapTiles ,它只是一个有四面墙的地图)。 然而,这些具有不同高度的幻影墙面垂直于真实的墙壁。 为什么?

请注意,白色“间隙”实际上并不是间隙:它试图绘制高度Infinity (距离0)的墙。 如果你专门考虑它(这个版本的代码没有),只是将它限制在屏幕高度,那么你只看到一个非常高的墙。

源代码如下。 它是简单的Haskell,使用Haste编译为JavaScript并渲染到画布。 它是基于C ++代码本教程中 ,但要注意的是我更换mapXmapYtileXtileY ,我没有ray的前缀posdir主循环中。 C ++代码中的任何差异都可能会破坏所有内容,但在多次仔细阅读此代码后,我似乎无法找到任何内容。

有帮助吗?

import Data.Array.IArray
import Control.Arrow (first, second)

import Control.Monad (forM_)

import Haste
import Haste.Graphics.Canvas

data MapTile = Empty | RedWall | BlueWall | GreenWall | YellowWall deriving (Eq)

type TilemapArray = Array (Int, Int) MapTile

emptyTilemapArray :: (Int, Int) -> TilemapArray
emptyTilemapArray dim@(w, h) = listArray ((1, 1), dim) $ replicate (w * h) Empty

testMapTiles :: TilemapArray
testMapTiles =
    let arr = emptyTilemapArray (16, 16)
        myBounds@((xB, yB), (w, h)) = bounds arr
    in  listArray myBounds $ flip map (indices arr) (\(x, y) ->
            if x == xB then RedWall
            else if y == yB then BlueWall
            else if x == w then GreenWall
            else if y == h then YellowWall
            else Empty)

type Vec2 a = (a, a)
type DblVec2 = Vec2 Double
type IntVec2 = Vec2 Int

add :: (Num a) => Vec2 a -> Vec2 a -> Vec2 a
add (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

mul :: (Num a) => Vec2 a -> a -> Vec2 a
mul (x, y) factor = (x * factor, y * factor)

rot :: (Floating a) => Vec2 a -> a -> Vec2 a
rot (x, y) angle =
    (x * (cos angle) - y * (sin angle), x * (sin angle) + y * (cos angle))

dbl :: Int -> Double
dbl = fromIntegral

-- fractional part of a float
-- `truncate` matches behaviour of C++'s int()
frac :: Double -> Double
frac d = d - dbl (truncate d)

-- get whole and fractional parts of a float
split :: Double -> (Int, Double)
split d = (truncate d, frac d)

-- stops 'Warning: Defaulting the following constraint(s) to type ‘Integer’'
square :: Double -> Double
square = (^ (2 :: Int))

-- raycasting algorithm based on code here:
-- http://lodev.org/cgtutor/raycasting.html#Untextured_Raycaster_

data HitSide = NorthSouth | EastWest deriving (Show)

-- direction, tile, distance
type HitInfo = (HitSide, IntVec2, Double)

-- pos: start position
-- dir: initial direction
-- plane: camera "plane" (a line, really, perpendicular to the direction)
traceRays :: TilemapArray -> Int -> DblVec2 -> DblVec2 -> DblVec2 -> [HitInfo]
traceRays arr numRays pos dir plane = 
    flip map [0..numRays] $ \x -> 
        let cameraX = 2 * ((dbl x) / (dbl numRays)) - 1
        in  traceRay arr pos $ dir `add` (plane `mul` cameraX)

traceRay :: TilemapArray -> DblVec2 -> DblVec2 -> HitInfo
traceRay arr pos@(posX, posY) dir@(dirX, dirY) =
    -- map tile we're in (whole part of position)
    -- position within map tile (fractional part of position)
    let ((tileX, fracX), (tileY, fracY)) = (split posX, split posY)
        tile = (tileX, tileY)
    -- length of ray from one x or y-side to next x or y-side
        deltaDistX = sqrt $ 1 + (square dirY / square dirX)
        deltaDistY = sqrt $ 1 + (square dirX / square dirY)
        deltaDist  = (deltaDistX, deltaDistY)
    -- direction of step
        stepX = if dirX < 0 then -1 else 1
        stepY = if dirY < 0 then -1 else 1
        step  = (stepX, stepY)
    -- length of ray from current position to next x or y-side
        sideDistX = deltaDistX * if dirX < 0 then fracX else 1 - fracX
        sideDistY = deltaDistY * if dirY < 0 then fracY else 1 - fracY
        sideDist  = (sideDistX, sideDistY)
        (hitSide, wallTile) = traceRayInner arr step deltaDist tile sideDist
    in  (hitSide, wallTile, calculateDistance hitSide pos dir wallTile step)

traceRayInner :: TilemapArray -> IntVec2 -> DblVec2 -> IntVec2 -> DblVec2 -> (HitSide, IntVec2)
traceRayInner arr step@(stepX, stepY) deltaDist@(deltaDistX, deltaDistY) tile sideDist@(sideDistX, sideDistY)
    -- a wall has been hit, report hit direction and coördinates
    | arr ! tile /= Empty   = (hitSide, tile)
    -- advance until a wall is hit
    | otherwise             = case hitSide of
        EastWest ->
            let newSideDist = first (deltaDistX+) sideDist
                newTile     = first (stepX+) tile
            in
                traceRayInner arr step deltaDist newTile newSideDist
        NorthSouth ->
            let newSideDist = second (deltaDistY+) sideDist
                newTile     = second (stepY+) tile
            in
                traceRayInner arr step deltaDist newTile newSideDist
    where
        hitSide = if sideDistX < sideDistY then EastWest else NorthSouth

-- calculate distance projected on camera direction
-- (an oblique distance would give a fisheye effect)
calculateDistance :: HitSide -> DblVec2 -> DblVec2 -> IntVec2 -> IntVec2 -> Double
calculateDistance EastWest (startX, _) (dirX, _) (tileX, _) (stepX, _) =
    ((dbl tileX) - startX + (1 - dbl stepX) / 2) / dirX
calculateDistance NorthSouth (_, startY) (_, dirY) (_, tileY) (_, stepY) =
    ((dbl tileY) - startY + (1 - dbl stepY) / 2) / dirY

-- calculate the height of the vertical line on-screen based on the distance
calculateHeight :: Double -> Double -> Double
calculateHeight screenHeight 0 = screenHeight
calculateHeight screenHeight perpWallDist = screenHeight / perpWallDist

width   :: Double
height  :: Double
(width, height) = (640, 480)

main :: IO ()
main = do
    cvElem <- newElem "canvas" `with` [
            attr "width" =: show width,
            attr "height" =: show height
        ]
    addChild cvElem documentBody
    Just canvas <- getCanvas cvElem
    let pos     = (8, 8)
        dir     = (-1, 0)
        plane   = (0, 0.66)
    renderGame canvas pos dir plane

renderGame :: Canvas -> DblVec2 -> DblVec2 -> DblVec2 -> IO ()
renderGame canvas pos dir plane = do
    let rays    = traceRays testMapTiles (floor width) pos dir plane
    render canvas $ forM_ (zip [0..width - 1] rays) (\(x, (side, tile, dist)) ->
        let lineHeight  = calculateHeight height dist
            wallColor   = case testMapTiles ! tile of
                RedWall     -> RGB 255 0 0
                BlueWall    -> RGB 0 255 0
                GreenWall   -> RGB 0 0 255
                YellowWall  -> RGB 255 255 0
                _           -> RGB 255 255 255
            shadedWallColor = case side of
                EastWest    -> 
                    let (RGB r g b) = wallColor
                    in  RGB (r `div` 2) (g `div` 2) (b `div` 2)
                NorthSouth  -> wallColor
        in  color shadedWallColor $ do
                translate (x, height / 2) $ stroke $ do
                    line (0, -lineHeight / 2) (0, lineHeight / 2))
    -- 25fps
    let fps             = 25
        timeout         = (1000 `div` fps) :: Int
        rots_per_min    = 1
        rots_per_sec    = dbl rots_per_min / 60
        rots_per_frame  = rots_per_sec / dbl fps
        tau             = 2 * pi
        increment       = tau * rots_per_frame 

    setTimeout timeout $ do
       renderGame canvas pos (rot dir $ -increment) (rot plane $ -increment)

HTML页面:

<!doctype html>
<meta charset=utf-8>
<title>Raycaster</title>

<noscript>If you're seeing this message, either your browser doesn't support JavaScript, or it is disabled for some reason. This game requires JavaScript to play, so you'll need to make sure you're using a browser which supports it, and enable it, to play.</noscript>
<script src=raycast.js></script>

===============>>#1 票数:3 已采纳

“幽灵面孔”正在发生,因为正在报告不正确的HitSide :你说面部是在水平移动( EastWest )上被击中,但实际上是在垂直移动( NorthSouth )上被击中,反之亦然。

为什么报告的值不正确呢? if sideDistX < sideDistY then EastWest else NorthSouth似乎非常万无一失,对吗? 它是。

问题不在于我们如何计算该值。 这是我们计算出的值。 距离计算功能需要知道我们移动到墙上的方向。 然而,我们实际给出的是我们要继续前进的方向(也就是说,如果那块瓷砖不是墙,或者我们因某种原因而忽略它)。

看看Haskell代码:

traceRayInner arr step@(stepX, stepY) deltaDist@(deltaDistX, deltaDistY) tile sideDist@(sideDistX, sideDistY)
    -- a wall has been hit, report hit direction and coördinates
    | arr ! tile /= Empty   = (hitSide, tile)
    -- advance until a wall is hit
    | otherwise             = case hitSide of
        EastWest ->
            let newSideDist = first (deltaDistX+) sideDist
                newTile     = first (stepX+) tile
            in
                traceRayInner arr step deltaDist newTile newSideDist
        NorthSouth ->
            let newSideDist = second (deltaDistY+) sideDist
                newTile     = second (stepY+) tile
            in
                traceRayInner arr step deltaDist newTile newSideDist
    where
        hitSide = if sideDistX < sideDistY then EastWest else NorthSouth

请注意,我们按此顺序执行操作:

  1. 计算hitSide
  2. 检查墙是否被击中,如果是,请报告hitSide
  3. 移动

将其与原始C ++代码进行比较:

//perform DDA
while (hit == 0)
{
  //jump to next map square, OR in x-direction, OR in y-direction
  if (sideDistX < sideDistY)
  {
    sideDistX += deltaDistX;
    mapX += stepX;
    side = 0;
  }
  else
  {
    sideDistY += deltaDistY;
    mapY += stepY;
    side = 1;
  }
  //Check if ray has hit a wall
  if (worldMap[mapX][mapY] > 0) hit = 1;
}

它以不同的顺序执行操作:

  1. 检查墙是否被击中,如果是,报告side (相当于hitSide
  2. 移动并计算side

C ++代码仅在移动时计算side ,然后如果它碰到墙,则报告该值。 因此,它报告了它为了碰壁而移动的方式。

Haskell的代码计算side它是否移动:所以它是正确的每一个举动,但是当它撞到一堵墙,它报告它会移动的方式为它继续下去。

因此,Haskell代码可以通过重新排序来修复,以便移动检查命中,如果是,则报告该移动的hitSide值。 这不是漂亮的代码,但它有效:

traceRayInner arr step@(stepX, stepY) deltaDist@(deltaDistX, deltaDistY) tile sideDist@(sideDistX, sideDistY) =
    let hitSide = if sideDistX < sideDistY then EastWest else NorthSouth
    in  case hitSide of
        EastWest ->
            let newSideDist = first (deltaDistX+) sideDist
                newTile     = first (stepX+) tile
            in  case arr ! newTile of
                -- advance until a wall is hit
                Empty   ->  traceRayInner arr step deltaDist newTile newSideDist
                -- a wall has been hit, report hit direction and coördinates
                _       ->  (hitSide, newTile)
        NorthSouth ->
            let newSideDist = second (deltaDistY+) sideDist
                newTile     = second (stepY+) tile
            in  case arr ! newTile of
                -- advance until a wall is hit
                Empty   ->  traceRayInner arr step deltaDist newTile newSideDist
                -- a wall has been hit, report hit direction and coördinates
                _       ->  (hitSide, newTile)

问题解决了!


旁注:我在纸上执行算法后弄清楚出了什么问题。 虽然在这种特殊情况下,最后两个HitSide值匹配,但显然它们可能并非在所有情况下都是如此。 所以,非常感谢Madsy对Freenode的#algorithms建议在纸上#algorithms :)

  ask by Andrea translate from so

未解决问题?本站智能推荐:

1回复

使我的画布正确显示图形

我试图创建一个图形计算器,并使其在“画布”上正确显示图形。 当我加载HTML文件并写入x时,例如,它从左上角开始,然后向下到右下角。 因此,问题在于它以颠倒的形式显示图形,并且不包含负值。 我知道画布从左上角的像素值(0,0)开始,到右下角的(300,300)结束。 我希望它通过此链接显
1回复

急速Haskell-> JS编译器在OSX上不起作用,在调用hastec时显示特定的错误消息

我已经尝试使用官方安装指南来设置Haste。 尝试编译Hello World会产生以下错误: 然后,我尝试编译便携式版本。 现在的错误是: 运行haste-boot不会修正该错误。
1回复

为什么我的Raycaster精灵会闪烁,如何解决?

我的raycaster的精灵一直闪烁,我认为这与ZBuffer有关。 使用此作为参考: https : //lodev.org/cgtutor/index.html 我进行了大量研究,找不到任何可以解决我问题的JS射线发生器。 精灵是指广告牌图片,当您移动它们时,它们就像是被推向极
2回复

显示标题栏时,ThreeJs raycaster的event.clientY值不正确

这是问题所在: 单击时,我想在3D模型上粘贴一个球体。 我使用ThreeJs raycaster来做到这一点。 如果画布覆盖整个页面,则效果很好,但是如果我在页面上添加标题栏,则效果很好。 以下是一些图形示例: 没有标题栏,球体将显示在鼠标指针下方,没问题: 确定 现在带
2回复

可视化Raycaster

我试图通过使用光线投射来检测交叉点。 我目前的问题是我不确定我的光线投射是否朝着理想的方向发展。 所以我的一般问题是:有没有办法让光线投射可见? 如果是这样:它是如何完成的? 这对我很有帮助。 迈克尔
1回复

动态更改Raycaster位置

我有一个画布,里面有几个立方体。 我使用Raycaster来选择它们并更改它们的颜色。 但是画布在draggable对象的内部,当我四处移动时我无法更改颜色,因此更改颜色的位置是原始的。 我认为我还必须更改Raycaster的尺寸。 我怎样才能做到这一点? 这是例子 。
1回复

C:Raycaster无法正常工作

我试图在终端上运行的C语言中制作一个ascii raycaster。 我认为以前没有做过这样的事情。 似乎无法正确打印框架。 希望有人能告诉我问题出在哪里。 这是我的代码: https : //ghostbin.com/paste/9vxmz我期望程序以第一人称显示迷宫(存储在地图数组中
1回复

使用raycaster在Click上隐藏对象

我想通过单击来隐藏场景对象,我阅读了许多“ raycaster”教程,但是当我打开HTML文件时,所有对象都被隐藏了,我不知道这些代码在哪里。 我已将大多数无关紧要的代码删除到raycaster。
1回复

Aframe禁用鼠标的raycaster

嘿社区/(Piotr Adam), 我的raycaster仅应与具有“ clickable”类的元素进行交互。 这可以正常工作,但是我的鼠标仍然激活每个元素,我如何让我的鼠标知道它应该与“可点击”元素交互? 这里有一些代码,但我想它不是必需的:
2回复

一个Three.js raycaster可以与一组相交吗?

我想知道我的raycaster是否在看我装的OBJ。 由于从Cinema4D导出的方式,我相信OBJ是一个有3个孩子的THREE.Group,而不是THREE.Object。 我可以只更改我的raycaster代码行来查找该组而不是对象吗? onOffCubes是一个包含6个OBJ