In a 2D top-down map it is sometimes useful to calculate which areas are visible from a given point. For example you might want to hide what’s not visible from the player’s location, or you might want to know what areas would be lit by a torch.
The walls
we start by defining the class Boundary. We can represent the boundaries with segments spread across the canvas.
A segment is constructed by setting up 2 points a and b. p5 vectors are nice to objects since they allow simple mathematical operations.
class Boundary {
constructor(x1, y1, x2, y2) {
this.a = createVector(x1, y1);
this.b = createVector(x2, y2);
}
show() {
stroke(255);
line(this.a.x, this.a.y, this.b.x, this.b.y);
}
}
We generate 5 walls randomly by iterating through a loop and pushing the instances of the boundary class into an array.
A light source
To mimic the behavior of a torch, we make a circle from which rays are casted.
This can be done in 2 steps : First, we make a Particule class. To simplify things, we consider for the time being that the particule is in the center of the canvas.
class Particle {
constructor() {
this.pos = createVector(width / 2, height / 2);
}
update(x, y) {
this.pos.set(x, y);
}
class Ray {
constructor(pos, angle) {
this.pos = pos;
this.dir = p5.Vector.fromAngle(angle);
}
lookAt(x, y) {
this.dir.x = x - this.pos.x;
this.dir.y = y - this.pos.y;
this.dir.normalize();
}
show() {
stroke(255);
push();
translate(this.pos.x, this.pos.y);
line(0, 0, this.dir.x * 10, this.dir.y * 10);
pop();
}
Next, a ray is represented by a segment, here we chose to construct the the segment with p5.Vector.fromAngle because it will later come in handy when we will need to generate N rays since the light rays are coming off the same point i.e the center of the particule. Thus, we will just need to replace the angle variable with 360 / N.
For example, for N = 360 we get :
Closest wall
We now come to the most challenging part in this project. When a ray is casted in all directions, it will go through the boundaries and continue its path. Why ? Remember we have not coded this part yet.
In fact, to stop the ray from going through the wall, we need to determine the closest wall at which the ray is projected.
To do this, determing the distance between 2 segments using their respective paramateric equations seems the straightforward solution.
First we consider the intersection of two lines L1 and L2 in two-dimensional space, with line L1 being defined by two distinct points (x1, y1) and (x2, y2), and line L2 being defined by two distinct points (x3, y3) and (x4, y4)
Source : wikipedia.org/wiki/Line–line_intersection
lookAt(x, y) {
this.dir.x = x - this.pos.x;
this.dir.y = y - this.pos.y;
this.dir.normalize();
}
show() {
stroke(255);
push();
translate(this.pos.x, this.pos.y);
line(0, 0, this.dir.x * 10, this.dir.y * 10);
pop();
}
cast(wall) {
const x1 = wall.a.x;
const y1 = wall.a.y;
const x2 = wall.b.x;
const y2 = wall.b.y;
const x3 = this.pos.x;
const y3 = this.pos.y;
const x4 = this.pos.x + this.dir.x;
const y4 = this.pos.y + this.dir.y;
const den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (den == 0) {
return;
}
const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
if (t > 0 && t < 1 && u > 0) {
const pt = createVector();
pt.x = x1 + t * (x2 - x1);
pt.y = y1 + t * (y2 - y1);
return pt;
} else {
return;
}
}
}
Finally, to move the particule around the canevas, we update the x and y coordinates of the particule with a simple noise function
let xoff = 0;
let yoff = 0;
particle.update(noise(xoff)*width, noise(yoff)*height);
xoff += 0.001;
yoff += 0.001