为什么我的光线投射引擎的鱼眼校正会导致凹壁?

时间:2016-12-09 08:54:52

标签: javascript html canvas raycasting

我正在使用HTML Canvas构建光线投射引擎的开头,我很快遇到了鱼眼效果问题。有很多网站告诉你只需将光线的长度乘以其与玩家角度的余弦相乘即可。

distance * Math.cos(angle)

但是,该修复仅适用于我的地图的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);

1 个答案:

答案 0 :(得分:0)

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

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