Raycasting с максимальным расстоянием - PullRequest
0 голосов
/ 28 февраля 2020

Я пишу простой raycast в htm5l, и основная проблема с raycast заключается в том, что линия уходит в направлении, вероятно, бесконечной длины. Я хотел бы ограничить эту длину указанным радиусом c, но я Мне не повезло. Если бы кто-то мог направить меня, это было бы здорово.

window.addEventListener('DOMContentLoaded', (event) => {
  let canvas = document.getElementById('canvas');
  let ctx = canvas.getContext('2d');
  let coord = {
    x: 0,
    y: 0
  }

  function line(x1, y1, x2, y2) {
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
  }
  class Vector {
    constructor(x, y) {
      this.x = x;
      this.y = y;
    }
  }
  class Boundery {
    constructor(x1, y1, x2, y2) {
      this.a = new Vector(x1, y1);
      this.b = new Vector(x2, y2);
    }
    show() {
      ctx.strokeStyle = '#000000'
      line(this.a.x, this.a.y, this.b.x, this.b.y);
    }
  }
  class Ray {
    constructor(x, y) {
      this.pos = new Vector(x, y);
      this.dir = new Vector(Math.cos(1), Math.sin(0));

    }

    show() {
      ctx.strokeStyle = '#000000';
      ctx.beginPath();
      ctx.ellipse(this.pos.x, this.pos.y, 5, 5, 0, 0, Math.PI * 2);
      ctx.stroke();

    }

    cast(wall) {
      let x1 = wall.a.x;
      let y1 = wall.a.y;
      let x2 = wall.b.x;
      let y2 = wall.b.y;

      let x3 = this.pos.x;
      let y3 = this.pos.y;
      let x4 = this.pos.x + this.dir.x;
      let y4 = this.pos.y + this.dir.y;

      let den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
      if (den == 0) {
        return;
      }
      let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
      let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;

      if (t > 0 && t < 1 && u > 0) {
        let point = new Vector(x1 + t * (x2 - x1), y1 + t * (y2 - y1));
        return point;

      } else {
        return;
      }
    }

  }

  let wall = new Boundery(300, 100, 300, 300);
  let ray = new Ray(100, 200);

  function tick(timestamp) {

    ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
    wall.show();
    ray.show();
    let r = ray.cast(wall);
    if (r) {
      ctx.fillStyle = 'red';
      ctx.ellipse(r.x, r.y, 10, 10, 0, 0, 2 * Math.PI);
      ctx.fill();
    }
    requestAnimationFrame(tick);
  }
  requestAnimationFrame(tick);
});
<canvas id="canvas" width="2000" height="1000"></canvas>

Таким образом, луч в настоящее время срабатывает справа (1,0) от маленького красного круга, но это расстояние просто продолжается навсегда, поэтому я пытаюсь ограничить это расстояние. В этом примере позиция, в которую луч попадает на стену, это красный круг, нарисованный на стене

1 Ответ

1 голос
/ 28 февраля 2020

Длина и направление луча

Измените луч, чтобы иметь начальную позицию, направление и длину. следующим образом

class Ray {
    constructor(pos, direction, length) {
        this.pos = pos;
        this.dir = direction;
        this.length = length;
    }

Конечную точку можно получить с помощью

    get end() {
        return new Vector(
            this.pos.x + Math.cos(this.dir) * this.length,
            this.pos.y + Math.sin(this.dir) * this.length
        );
    }

. Когда вы cast пересекаете луч, вы преобразуете его в линейный сегмент, а затем проверяете по любым сегментам стены на перехват. Будут найдены только точки с длиной луча.

Пример.

В примере используется луч для проверки на многие стены. Он находит ближайший перехват к началу луча и в пределах длины луча.

Примечание (только для примера) стены случайны, поэтому, если стена приближается к Луч, кликни на холст, чтобы рандомизировать стены.

Я немного реорганизовал его с Vector в качестве точки, Line (2 вектора) в виде отрезка с функцией перехвата (также представляет собой стена), а Ray как вектор, направление и длина. Ray.cast находит перехват массива строк, возвращая неопределенное значение, если перехват не найден.

const ctx = canvas.getContext("2d");
Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
var myRay;
const WALL_COUNT = 30;
const WALL_STYLE = {radius: 0, lineWidth: 1, strokeStyle: "#000"};
const RAY_STYLE_A = {radius: 2, lineWidth: 1, strokeStyle: "#0FF", fillStyle: "#F00"};
const RAY_STYLE_B = {radius: 5, lineWidth: 3, strokeStyle: "#00F", fillStyle: "#F00"};
const RAY_INTERCEPT_STYLE = {radius: 5, lineWidth: 1, strokeStyle: "#000", fillStyle: "#FF0"};
const ROTATE_RAY = 10; // seconds per rotation
const walls = [];
setTimeout(init, 0);
canvas.addEventListener("click",init);

class Vector {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
        ctx.strokeStyle = strokeStyle;
        ctx.fillStyle = fillStyle;
        ctx.lineWidth = lineWidth;
        ctx.beginPath();
        ctx.arc(this.x, this.y, radius, 0, Math.TAU);
        ctx.fill();
        ctx.stroke();       
    }
}
class Line {
    constructor(start, end) {
        this.start = start;
        this.end = end;
    }
    draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
        if (radius > 0) {
            this.start.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
            this.end.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
        }
        ctx.strokeStyle = strokeStyle;
        ctx.lineWidth = lineWidth;
        ctx.beginPath();
        ctx.moveTo(this.start.x, this.start.y);
        ctx.lineTo(this.end.x, this.end.y);
        ctx.stroke();
    }
    intercept(line) {
        var x1, y1, x2, y2, x3, y3, c, u;
        x1 = line.end.x - line.start.x;
        y1 = line.end.y - line.start.y;
        x2 = this.end.x - this.start.x;
        y2 = this.end.y - this.start.y;
        c = x1 * y2 - y1 * x2;
        if (c) {
            x3 = line.start.x - this.start.x;
            y3 = line.start.y - this.start.y;
            u = (x1 * y3 - y1 * x3) / c;
            if (u >= 0 && u <= 1) {
                u = (x2 * y3 - y2 *x3) / c;
                if (u >= 0 && u <= 1) { return [u, line.start.x + x1 * u, line.start.y + y1 * u]  }
            }
        }
    }
}
class Ray {
    constructor(pos, direction, length) {
        this.pos = pos;
        this.dir = direction;
        this.length = length;
    }

    draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
        this.pos.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
        ctx.strokeStyle = strokeStyle;
        ctx.lineWidth = lineWidth;
        ctx.beginPath();
        ctx.moveTo(this.pos.x, this.pos.y);
        ctx.lineTo(
            this.pos.x + Math.cos(this.dir) * this.length,
            this.pos.y + Math.sin(this.dir) * this.length
        );
        ctx.stroke();
    }
    get end() {
        return new Vector(
            this.pos.x + Math.cos(this.dir) * this.length,
            this.pos.y + Math.sin(this.dir) * this.length
        );
    }
    get line() {
        return new Line(this.pos, this.end);
    }
    cast(lines) {
        const tLine = this.line;
        var minDist = 1, point;
        for (const line of lines) {
            const result = line.intercept(tLine);
            if (result) {
                const [u, x, y] = result;
                if (u <= minDist) {
                    minDist = u;
                    if (!point) { point = new Vector(x, y) }
                    else { 
                        point.x = x;
                        point.y = y;
                    }
                     point.u = u;
                }
            }
        }
        return point;
    }
}


function init() {
    walls.length = 0;

    for (let i = 0; i < WALL_COUNT / 2; i++) {
        walls.push(new Ray(
          new Vector(Math.rand(0, canvas.width * 0.4), Math.rand(0, canvas.height)),
          (Math.rand(0, 8) | 0) / 4 * Math.PI, 100
        ).line);
        walls.push(new Ray(
          new Vector(Math.rand(canvas.width * 0.6, canvas.width), Math.rand(0, canvas.height)),
          (Math.rand(0, 8) | 0) / 4 * Math.PI, 100
        ).line);
    }
    if(!myRay) {
        myRay = new Ray(new Vector(canvas.width / 2, canvas.height / 2), 0, Math.max(canvas.width, canvas.height) * 0.485);    
        requestAnimationFrame(mainLoop);
    }
        
}

function mainLoop(time) {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    myRay.dir = (time / (ROTATE_RAY * 1000)) * Math.TAU;
    const point = myRay.cast(walls)
    myRay.draw(ctx, RAY_STYLE_A);
    for(const w of walls) { w.draw(ctx, WALL_STYLE) }
    if (point) {
        const len = myRay.length;
        myRay.length = point.u * len;
        myRay.draw(ctx, RAY_STYLE_B);
        myRay.length = len;
        point.draw(ctx, RAY_INTERCEPT_STYLE);
    }
    requestAnimationFrame(mainLoop);
}

        
#canvas {
  border: 2px solid black;
}
 <canvas id="canvas" width="500" height="500">  </canvas>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...