我的2D光线跟踪功能出了什么问题?

时间:2018-08-13 00:09:10

标签: javascript raytracing

我编写了一个简单的函数,该函数使用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增量)。我的算法有什么问题?

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);