javascript - 为什么我的光线转换引擎鱼眼校正会导致凹墙?

标签 javascript html canvas raycasting

我正在使用 HTML Canvas 构建光线转换引擎的雏形,但很快就遇到了鱼眼效果问题。有很多网站告诉您只需将光线的长度乘以与玩家的 Angular 余弦即可纠正它。

distance * Math.cos(angle)

但是,该修复仅适用于 map 的 y 轴。在 x 轴上,它使墙壁基本上产生与鱼眼效果相反的效果。知道为什么会发生这种情况吗?

这是我的代码:

var c = document.getElementById('canvas');
var ctx = c.getContext('2d');

c.height = window.innerHeight;
c.width = window.innerWidth;

//Setup up map
var map = [];
for(var i = 0;i < 20;i++)
{
    map[i] = [];
}
map[0] = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
map[1] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[2] = [2, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 2];
map[3] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[4] = [2, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 2];
map[5] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[6] = [2, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 2];
map[7] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[8] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[9] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[10] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[11] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[12] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[13] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[14] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[15] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[16] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[17] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[18] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
map[19] = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];

var character = {
    x: c.width / 2,
    y: (c.height / 2) + 100,
    r: 25,
    angle: 0
};

//Define all rays
var rays = [];
for(var i = 0;i < 300;i++)
{
    rays[i] = {
        x: 0,
        y: 0,
        travelling: false,
        hit: false,
        type: 0
    };
}

//GET INPUT
var turningLeft = false,
    turningRight = false,
    movingUp = false,
    movingDown = false;

window.addEventListener('keydown', handleKeyDown, true);
window.addEventListener('keyup', handleKeyUp, true);
function handleKeyDown(e)
{
    switch(e.keyCode)
    {
        case 87: movingUp = true;
            break;
        case 83: movingDown = true;
            break;
        case 65: turningLeft = true;
            break;
        case 68: turningRight = true;
            break;
    }
}
function handleKeyUp(e)
{
    switch(e.keyCode)
    {
        case 87: movingUp = false;
            break;
        case 83: movingDown = false;
            break;
        case 65: turningLeft = false;
            break;
        case 68: turningRight = false;
            break;
    }
}

function gameLoop()
{
    update();
    render();
    
    window.requestAnimationFrame(gameLoop);
}

function update()
{
        
    //Allow movement
    if(movingUp)
    {
        if(!detectCharacterCollision(character.x + Math.cos(character.angle) * 2, character.y))
        {
            character.x += Math.cos(character.angle) * 2;
        }
        if(!detectCharacterCollision(character.x, character.y + Math.sin(character.angle) * 2))
        {
            character.y += Math.sin(character.angle) * 2;
        }
    }
    if(movingDown)
    {
        character.x -= Math.cos(character.angle);
        character.y -= Math.sin(character.angle);
    }
    if(turningLeft)
    {
        character.angle -= Math.PI / 180;
    }
    if(turningRight)
    {
        character.angle += Math.PI / 180;
    }
        
    //Cast ray
    for(var i = 0;i < rays.length;i++)
    {
        rays[i] = {
            x: character.x,
            y: character.y,
            travelling: true,
            hit: false,
            type: 0
        };
        
        //Until the ray hits a wall
        while(rays[i].travelling)
        {
            //Detect if ray has hit a wall
            var collision = detectRayCollision(rays[i].x, rays[i].y);
            //Collision has the type of wall which was collided with (0 for no wall)
            if(collision == 1)
            {
                rays[i].travelling = false;
                rays[i].hit = true;
                rays[i].type = 1;
            }
            else if(collision == 2)
            {
                rays[i].travelling = false;
                rays[i].hit = true;
                rays[i].type = 2;
            }
            else
            {
                //If nothing was hit, move ray is appropriate direction from player
                var angle = (i * ((Math.PI / 2) / rays.length)) - (Math.PI / 4);
                
                rays[i].x += Math.cos(character.angle + angle);
                rays[i].y += Math.sin(character.angle + angle);
            }
        }
    }
}

function detectRayCollision(x, y)
{
    return map[Math.trunc(y / (c.height / 20))][Math.trunc(x / (c.width / 20))];
}

function detectCharacterCollision(x, y)
{
    if(map[Math.trunc(y / (c.height / 20))][Math.trunc(x / (c.width / 20))] == 0)
    {
        return false;
    }
    else
    {
        return true;
    }
}

function getTime()
{
    var date = new Date();
    return date.getTime();
}

function render()
{
    ctx.clearRect(0, 0, c.width, c.height);
    
    //Skybox
    ctx.beginPath();
    ctx.rect(0, 0, c.width, c.height / 2);
    ctx.fillStyle = 'rgb(135,206,250)';
    ctx.fill();
    ctx.closePath();
    
    //Floor
    ctx.beginPath();
    ctx.rect(0, c.height / 2, c.width, c.height / 2);
    ctx.fillStyle = 'black';
    ctx.fill();
    ctx.closePath();
    
    for(var i = 0;i < rays.length;i++)
    {
        var dx = rays[i].x - character.x;
        var dy = rays[i].y - character.y;
        var angle = Math.atan2(dy, dx);
        var distance = Math.sqrt((dy * dy) + (dx * dx));
        var z = distance * Math.cos(angle);
        
        ctx.beginPath();
        
        //Set color (or texture) for wall
        if(rays[i].type == 1)
        {
            ctx.fillStyle = 'grey';
        }
        else if(rays[i].type == 2)
        {
            ctx.fillStyle = 'orange';
        }
        
        ctx.fillRect(i * (c.width / rays.length), (c.height / 2) - ((c.height / (z / 100)) / 2), c.width / rays.length + 1, c.height / (z / 100));
        
        ctx.closePath();
    }
}

window.requestAnimationFrame(gameLoop);

最佳答案

我已经回答了我自己的问题。我没有考虑玩家当前面临的方向。所以在我的代码中,鱼眼校正的行应该是:

var z = distance * Math.cos(angle - character.angle);

关于javascript - 为什么我的光线转换引擎鱼眼校正会导致凹墙?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41056542/

相关文章:

javascript - 使用 VueRouter 为子组件分配 refs - Vue

javascript - Internet Explorer 中的 HTML5 元素 : runtime insertion

css - HTML:拆分父 div "fluidly"

javascript - 使用 jquery 的 float 表单

ruby Canvas (GUI)

javascript - 错误类型错误 : Cannot read property 'push' of null

javascript - 如何在文本框中输入数字并使其成为链接的一部分?

javascript - 删除前面的标签

Javascript 在 Canvas 上循环创建图像

javascript - node.js 意外标记; Canvas