Setting raycast directions in a circle using HTML Canvas 2D

Viewed 80

I am trying to implement raycasting in typescript with HTML Canvas 2D following this video: https://youtu.be/TOEi6T2mtHo. However, the rays being cast tend to point towards a single direction.

My goal is to have the rays extend out in each direction to make contact with each line like in the video.

I have tried normalizing the direction vector, and I have tried multiplying the direction of the rays with a scalar value like 10 but both did not work.

enter image description here gif of html 2D raycast

The rays are working sometimes, but mostly point in a single direction.

Here is the code for the Vector class:

export default class Vector2D {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  getX(): number {
    return this.x;
  }

  getY(): number {
    return this.y;
  }

  setX(x: number) {
    this.x = x;
  }

  setY(y: number) {
    this.y = y;
  }

  add(vector: Vector2D): Vector2D {
    return new Vector2D(this.x + vector.getX(), this.y + vector.getY());
  }

  subtract(vector: Vector2D): Vector2D {
    return new Vector2D(this.x - vector.getX(), this.y - vector.getY());
  }

  magnitude(): number {
    return Math.sqrt(this.x ** 2 + this.y ** 2);
  }

  normalize(): Vector2D {
    const magnitude = this.magnitude();
    this.x = this.x / magnitude;
    this.y = this.y / magnitude;
    return this;
  }

  dot(vec: Vector2D): number {
    return this.x * vec.getX() + this.y * vec.getY();
  }
}

Here is the code of the Ray class:

import Vector2D from "../Util/Vector2D";
import Line from "./Line";

export interface RayOptions {
  context: CanvasRenderingContext2D;
  position: Vector2D;
  direction: Vector2D;
}

export default class Ray {
  context: CanvasRenderingContext2D;
  position: Vector2D;
  direction: Vector2D;

  constructor({ context, position, direction }: RayOptions) {
    this.context = context;
    this.position = position;
    this.direction = direction;
  }

  getDirection(): Vector2D {
    return this.direction;
  }

  getPosition(): Vector2D {
    return this.position;
  }

  setDirection(vector: Vector2D): void {
    this.direction = vector.add(this.position);
    this.direction.normalize();
  }

  setPosition(vector: Vector2D): void {
    this.position = vector;
  }

  draw() {
    this.context.strokeStyle = "white";
    this.context.save();
    this.context.beginPath();
    this.context.translate(this.position.getX(), this.position.getY());
    this.context.moveTo(0, 0);
    this.context.lineTo(this.direction.getX() * 10, this.direction.getY() * 10);
    this.context.stroke();
    this.context.restore();
  }

  cast(line: Line): Vector2D | null {
    const x1 = line.getStart().getX();
    const y1 = line.getStart().getY();
    const x2 = line.getEnd().getX();
    const y2 = line.getEnd().getY();
    const x3 = this.getPosition().getX();
    const y3 = this.getPosition().getX();
    const x4 = this.getPosition().getX() + this.getDirection().getX();
    const y4 = this.getPosition().getY() + this.getDirection().getY();

    const denomenator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
    if (denomenator === 0) {
      return null;
    }

    const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denomenator;
    const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denomenator;

    if (t > 0 && t < 1 && u > 0) {
      const x = x1 + t * (x2 - x1);
      const y = y1 + t * (y2 - y1);

      return new Vector2D(x, y);
    }
    return null;
  }
}

Here is the code for the Line class:

import Vector2D from "../Util/Vector2D";

export interface LineOptions {
  context: CanvasRenderingContext2D;
  start: Vector2D;
  end: Vector2D;
  color?: string;
}

export default class Line {
  context: CanvasRenderingContext2D;
  start: Vector2D;
  end: Vector2D;
  color: string;

  constructor({
    context,
    color = "rgb(255, 255, 0)",
    start,
    end
  }: LineOptions) {
    this.context = context;
    this.start = start;
    this.end = end;
    this.color = color;
  }

  setStart(vector: Vector2D): void {
    this.start = vector;
  }

  setEnd(vector: Vector2D): void {
    this.end = vector;
  }

  setColor(color: string): void {
    this.color = color;
  }

  getColor(): string {
    return this.color;
  }

  getStart(): Vector2D {
    return this.start;
  }

  getEnd(): Vector2D {
    return this.end;
  }

  draw(): void {
    this.context.strokeStyle = this.color;
    this.context.beginPath();
    this.context.moveTo(this.start.getX(), this.start.getY());
    this.context.lineTo(this.end.getX(), this.end.getY());
    this.context.stroke();
  }
}

Here is the Player class which contains the code for rendering the lines from the rays:

import Vector2D from "../Util/Vector2D";
import Circle, { CircleOptions } from "./Circle";
import Line from "./Line";
import Ray from "./Ray";

export default class Player extends Circle {
  rays: Ray[];
  constructor({ context, color, position, radius, velocity }: CircleOptions) {
    super({ context, color, position, radius, velocity });
    this.rays = [];
    this.setRays();
  }

  draw(): void {
    this.rays.forEach((ray) => ray.draw());
  }

  getRays(): Ray[] {
    return this.rays;
  }

  setPosition(vector: Vector2D): void {
    super.setPosition(vector);
    this.rays.forEach((ray) => ray.setPosition(vector));
  }

  castTo(lines: Line[]) {
    this.rays.forEach((ray) => {
      let record = Infinity;
      let closest: Vector2D = null!;
      lines.forEach((line) => {
        const intersection = ray.cast(line);
        if (intersection) {
          const diffX = intersection.getX() - this.getPosition().getX();
          const diffY = intersection.getY() - this.getPosition().getY();
          const distance = Math.hypot(diffX, diffY);
          if (distance < record) {
            record = distance;
            closest = intersection;
          }
        }
      });
      if (closest) {
        const l = new Line({
          context: this.context,
          color: "green",
          start: ray.getPosition(),
          end: closest
        });
        l.draw();
      }
    });
  }

  private setRays() {
    for (let i = 0; i < 360; i += 10) {
      const rad = i * (Math.PI / 180);
      this.rays.push(
        new Ray({
          context: this.context,
          position: this.position,
          direction: new Vector2D(Math.cos(rad), Math.sin(rad))
        })
      );
    }
  }
}

And finally, here is how I am updating the position for the rays:

  const updatePlayer = (ev: MouseEvent) => {
    if (player) {
      player.setPosition(
        new Vector2D(ev.clientX + offsetX, ev.clientY + offsetY)
      );
    }
  };

  window.addEventListener("mousemove", updatePlayer);
1 Answers

I found the error eventually in the Ray Class. The x3 and y3 variable were being set to the position of the X of the vector instead of the x and y.

  cast(line: Line): Vector2D | null {
    const x1 = line.getStart().getX();
    const y1 = line.getStart().getY();
    const x2 = line.getEnd().getX();
    const y2 = line.getEnd().getY();
    const x3 = this.getPosition().getX();
    //fixed this next line
    const y3 = this.getPosition().getY(); 
    const x4 = this.getPosition().getX() + this.getDirection().getX();
    const y4 = this.getPosition().getY() + this.getDirection().getY();
   
    const denomenator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
    if (denomenator === 0) {
      return null;
    }

    const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denomenator;
    const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denomenator;

    if (t > 0 && t < 1 && u > 0) {
      const x = x1 + t * (x2 - x1);
      const y = y1 + t * (y2 - y1);

      return new Vector2D(x, y);
    }
    return null;
  }
Related