RayCasting функция не работает

У меня есть объект с данными об игроке и объект с информацией для raycasting'а:

const player = {
    x: HALF_WIDTH,
    y: HALF_HEIGHT,
    angle: 0,
    speed: 2,
};

const ray = {
    FOV: Math.PI / 3,
    HALF_FOV: this.FOV / 2,
    NUM_RAYS: 80,
    MAX_DEPTH: 400,
    DELTA_ANGLE: this.FOV / this.NUM_RAYS,
};

Ещё у меня есть массив map, где хранятся начальные и конечные точки стен по x и у. И у меня есть функция, которая рисует на карте лучи, исходящие от игрока:

function rayCasting(player, ray) {
    let curAngle = player.angle - ray.HALF_FOV;
    let xo = player.x;
    let yo = player.y;

    ctx.strokeStyle = 'darkgray';

    for (let rayNum = 0; rayNum < ray.NUM_RAYS; rayNum++) {
        sinAngle = Math.sin(curAngle);
        cosAngle = Math.cos(curAngle);

        let lastX = lastY = 400;

        for (let depth = 0; depth < ray.MAX_DEPTH; depth++) {
            let x = (xo + depth) * cosAngle;
            let y = (yo + depth) * sinAngle;

            if (Math.floor(x) === map.x || Math.floor(y) === map.y || Math.floor(x) === map.lastX || Math.floor(y) === map.lastY) {
                lastX = x;
                lastY = y;
                break;
            } 
        }

        ctx.beginPath();
        ctx.moveTo(xo, yo);
        ctx.lineTo(lastX, lastY);
        ctx.stroke();

        curAngle += ray.DELTA_ANGLE;
    }
}

К сожалению, функция, которая отвечает за рисование лучей, работает некорректно. Где может быть ошибка и как исправить эту функцию?


Ответы (1 шт):

Автор решения: OPTIMUS PRIME

Три ошибки:

  1. Внутри объекта нельзя ссылаться на самого себя во время инициализации. В вашем коде
const ray = {
  FOV: Math.PI / 3,
  HALF_FOV: this.FOV / 2,
  NUM_RAYS: 80,
  MAX_DEPTH: 400,
  DELTA_ANGLE: this.FOV / this.NUM_RAYS,
};

this - глобальный объект.

Подобный объект можно создавать через конструктор, где this будет ссылаться на созданный объект при вызове:

const ray = new Ray();

function Ray() {
  this.FOV = Math.PI / 3;
  this.HALF_FOV = this.FOV / 2;
  this.NUM_RAYS = 80;
  this.MAX_DEPTH = 400;
  this.DELTA_ANGLE = this.FOV / this.NUM_RAYS;
}

Или так:

const ray = {
  FOV: Math.PI / 3,
  NUM_RAYS: 80,
  MAX_DEPTH: 400,
};

ray.HALF_FOV = ray.FOV / 2;
ray.DELTA_ANGLE = ray.FOV / ray.NUM_RAYS;
  1. let lastX = lastY = 400; — здесь lastY не объявляется. В режиме "use strict" выдало бы ошибку, если lastY где-то заранее не объявлен. Должно быть let lastX = 400, lastY = 400;

  2. Не предоставили воспроизводимый пример, пришлось додумывать!))

let ctx = canvas.getContext("2d");

const map = { x: 0, lastX: 200, _x: [0, 200], y: 0, lastY: 100, _y: [0, 100] };
const player = { x: 50, y: 50, angle: 0, speed: 2 };
const ray = new Ray();

rayCasting(player, ray);

/***/
function rayCasting(player, ray) {  
  let curAngle = player.angle - ray.HALF_FOV;
  let xo = player.x;
  let yo = player.y;
    
  /***/
  for (let rayNum = 0; rayNum < ray.NUM_RAYS; rayNum++) {
    let sin = Math.sin(curAngle);
    let cos = Math.cos(curAngle);
    
    curAngle += ray.DELTA_ANGLE;
    
    let lastX = 400, lastY = 400;

    for (let depth = 0; depth < ray.MAX_DEPTH; depth++) {
      let x = (xo + depth) * cos;
      let y = (yo + depth) * sin;

      if (map._x.includes(x|0) || map._y.includes(y|0)) {
        lastX = x;
        lastY = y;
        break;
      }
    }

    /***/
    ctx.strokeStyle = 'darkgray';
    ctx.beginPath();
      ctx.moveTo(xo, yo);
      ctx.lineTo(lastX, lastY);
    ctx.stroke();
  }
}

function Ray() {
  this.FOV = Math.PI / 3;
  this.HALF_FOV = this.FOV / 2;
  this.NUM_RAYS = 80;
  this.MAX_DEPTH = 400;
  this.DELTA_ANGLE = this.FOV / this.NUM_RAYS;
}
<canvas id="canvas"></canvas>

По поводу цепочки проверок Math.floor(x) === map.x ... Если число положительное, Math.floor(x) можно заменить на x|0 (побитовое "или", ноль - ничего не делает, операция отбрасывает дробную часть).

А в сам объект map можно добавить дополнительные свойства - массивы, хранящие оба значения: [x, lastX] и [y, lastY], чтобы проверять наличие числа в массиве, переписав всю проверку так: map._x.includes(x|0) || map._y.includes(y|0). Это не то, что правильнее, просто длинный вариант не понравился, подумал, как бы можно было сократить)

→ Ссылка