我编写了一个简单的函数,该函数使用DDA算法来实现2D射线跟踪(名为findCollision
),但是在某些情况下它似乎很奇怪;
function findCollision(position, direction, world, maxDistance = 10) {
const steps = Math.max(
Math.abs(direction.x),
Math.abs(direction.y),
);
const increment = {
x: direction.x / steps,
y: direction.y / steps,
};
let x = position.x;
let y = position.y;
for (let i = 0; i < maxDistance; i++) {
const roundedX = Math.round(x);
const roundedY = Math.round(y);
if (world[roundedX] && world[roundedX][roundedY]) {
return { x, y };
}
x += increment.x;
y += increment.y;
}
return null;
}
const CELL_SIZE = 32;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const center = { x: 4, y: 4 };
const map = [
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
];
Object.assign(canvas, {
width: 500,
height: 500,
});
function draw() {
for (let i = 0; i < Math.PI * 2; i += Math.PI / 32) {
const dX = Math.cos(i);
const dY = Math.sin(i);
const collision = findCollision(center, { x: dX, y: dY }, map, 64);
if (!collision) {
continue;
}
context.strokeStyle = 'rgba(0, 0, 0, 0.5)';
context.beginPath();
context.moveTo(center.x * CELL_SIZE, center.y * CELL_SIZE);
context.lineTo(collision.x * CELL_SIZE, collision.y * CELL_SIZE);
context.stroke();
context.fillStyle = 'rgba(0, 0, 0, 0.1)';
context.fillRect(collision.x * CELL_SIZE, collision.y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
}
}
requestAnimationFrame(draw);
document.body.appendChild(canvas);
从输出中可以看到,任何指向画布顶部或左侧的光线都超出碰撞区域1格(即1增量)。我的算法有什么问题?
答案 0 :(得分:2)
您的功能似乎正常。
第一个问题是您的世界没有中心。我添加了一列和一行。这样一来,图片应该对称且美观。
第二个问题是墙图。宽度和高度始终为正,会导致壁从一个方向的中心传播到另一个方向的中心。我根据方向添加了宽度和高度符号计算(但是部分墙壁超出了画布边界)。
更新
我扩大了墙壁可见的世界。看来算法有精度问题。如果我将观察者设置为不在中心,则拐角处的点看起来不太好。对于延迟求解,您可以使算法中的步骤明显短于网格步骤。
目前,一种算法仅执行下一条仅垂直或仅水平的网格线。您可以尝试更改算法,使其步进到下一条最接近的网格线。
function findCollision(position, direction, world, maxDistance = 10) {
const steps = Math.max(
Math.abs(direction.x),
Math.abs(direction.y),
);
const increment = {
x: direction.x / steps,
y: direction.y / steps,
};
let x = position.x;
let y = position.y;
for (let i = 0; i < maxDistance; i++) {
const roundedX = Math.round(x);
const roundedY = Math.round(y);
if (world[roundedX] && world[roundedX][roundedY]) {
return { x, y };
}
x += increment.x;
y += increment.y;
}
return null;
}
const CELL_SIZE = 32;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const center = { x: 4, y: 4 };
const map = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
Object.assign(canvas, {
width: 500,
height: 500,
});
function draw() {
for (let i = 0; i < Math.PI * 2; i += Math.PI / 32) {
const dX = Math.cos(i);
const dY = Math.sin(i);
const collision = findCollision(center, { x: dX, y: dY }, map, 64);
if (!collision) {
continue;
}
context.strokeStyle = 'rgba(0, 0, 0, 0.5)';
context.beginPath();
context.moveTo(center.x * CELL_SIZE, center.y * CELL_SIZE);
context.lineTo(collision.x * CELL_SIZE, collision.y * CELL_SIZE);
context.stroke();
context.fillStyle = 'rgba(0, 0, 0, 0.1)';
context.fillRect(collision.x * CELL_SIZE, collision.y * CELL_SIZE,
dX / Math.abs(dX) * CELL_SIZE,
dY / Math.abs(dY) * CELL_SIZE);
}
}
requestAnimationFrame(draw);
document.body.appendChild(canvas);