Coding an animation of balls colliding against the walls but some balls (~5%) pass right through the walls. Using p5.js of JavaScript

Problem exactly as the title says. I used the following pdf as guide to calculate the velocities after collision: (Pages 2 and 3) https://imada.sdu.dk/~rolf/Edu/DM815/E10/2dcollisions.pdf
Consider all balls to be of equal mass. I am using p5.js Web Editor.
I have thoroughly commented the code to make it easily understandable. Please ask me if something is unclear.
Also, I think the main problem must be in the move() function of class ball but I am copying the whole code anyways because most of the time problems lies where we don't expect it.

let sides=4 //no of sides of a regular polygon
let r  // distance of the vertices of the polygon from center
let points=[]  //
let arrayofLines=[]
let arrayofBalls=[]
let noofBalls=100
function setup() {
  createCanvas(600, 600);
  angleMode(DEGREES)
  colorMode(HSB)
  r=width/3
  for (let i=1;i<=sides;i++)points.push(createRegularPoints(r,i*360/sides)) // creating vertices of regular polygon
  createRegularPolygon() //drawing regular polygon
  for(let i=0;i<noofBalls;i++)arrayofBalls.push(new ball()) // creating balls
}

function draw() {
  background(20);
  stroke(0)
  strokeWeight(8)

  for(let i=0;i<arrayofLines.length;i++)arrayofLines[i].show()
  for(let b of arrayofBalls){
    b.move()
    b.show()
  }
}


function createRegularPoints(r,a){
  return [width/2+r*cos(a),height/2+r*sin(a)]
}

function createRegularPolygon(){
  let i
  for(i=0;i<points.length-1;i++)arrayofLines.push(new arrayofLineses(points[i][0],points[i][1],points[i+1][0],points[i+1][1]))  //send x and y of i and i+1 point to create a line
  arrayofLines.push(new arrayofLineses(points[i][0],points[i][1],points[0][0],points[0][1]))  //last line is joint with the first line
}
class arrayofLineses {
  constructor(x1,y1,x2,y2) {
    this.p1=[x1,y1]
    this.p2=[x2,y2]
  }
  show() {
    push();
    stroke(400);
    strokeWeight(4);
    line(this.p1[0], this.p1[1], this.p2[0], this.p2[1]);
    pop();
  }
}
class ball {
  constructor() {
    this.x = random(0, width);
    this.y = random(0, height);
    this.speedx = random(-2, 2);
    this.speedy = random(-2, 2);
    this.c = map(dist(0, 0, this.x, this.y), 0, sqrt(2) * height, 0, 360); //just mapping for color of balls
  }
  show() {
    push();
    noStroke();
    fill(this.c, 80, 80);
    circle(this.x, this.y, 5);
    pop();
  }
  move() {
    if (this.x <= 0 || this.x >= width) this.speedx *= -1; //collision against vertical walls
    if (this.y <= 0 || this.y >= height) this.speedy *= -1; //collision against horizontal walls
    let x1 = this.x;        //these (x1,y1) and (x2,y2) now form a line in the direction of balls movment
    let y1 = this.y;
    let x2 = this.x + this.speedx;
    let y2 = this.y + this.speedy;
    for (let l of arrayofLines) { //checking collision against each line
      let x3 = l.p1[0];   //again two end points of the line
      let y3 = l.p1[1];
      let x4 = l.p2[0];
      let y4 = l.p2[1];

      
      //Here I calculate the intersection between two lines using two point form(formula from wiki). So x and y are collision coordinates
      let D = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
      let x =
        ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / D;
      let y =
        ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / D;
      
      // checking if the collision point x,y actually lies on the line segment
      if (
        dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
        dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1]) 
      ) {
        
        //checking if the ball is close enough to line to be a called colliding
        let xdis = x - this.x;
        let ydis = y - this.y;
        if (xdis * xdis + ydis * ydis < 5 * 5) {
          
          //From here on, I used the mentioned pdf but doesn't use the vectors. Instead I create x and y for every vector mentioned.
          
          //normal vector (perpendicular to line, so slope of normal= -1/(slope of line))
          ydis = (l.p1[0] - l.p2[0]);
          xdis = -(l.p1[1] - l.p2[1]);
          
          let magn = sqrt(xdis * xdis + ydis * ydis);
          xdis /= magn;
          ydis /= magn;
          
          let utx = -ydis;
          let uty = xdis;
          let v1n = xdis * this.speedx + ydis * this.speedy;
          let v1t = utx * this.speedx + uty * this.speedy;
          v1n = -v1n;
          let v1nx = v1n * xdis;
          let v1ny = v1n * ydis;
          let v1tx = v1t * utx;
          let v1ty = v1t * uty;
          let vfx = v1nx + v1tx;
          let vfy = v1ny + v1ty;
          this.speedx = vfx;
          this.speedy = vfy;
        }
      }
      
    }
    
    this.x += this.speedx;
    this.y += this.speedy;
  }
}

Solution 1:

I believe the primary issue stems from your method of determining if the point of intersection between the balls trajectory and the lines that make up the box actually lies within the line that makes up the border of the box. Your code appears to sum the distance from one end point of the box border line to the intersection point with the distance from the other end of the box border line to intersection point, and then check if that total distance is less than or equal to the total length of that border edge:

     /*   end 1 to collision   */   /*   end 2 to collision   */
  if(dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
        /*       total box edge length.      */
        dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1])) {
    // ...
  }

Needless to say the calculated distance sum here should never be less than the length of the line, it will always be greater than or equal to. Any time you are dealing with floating point numbers and doing something only when two calculated values are exactly equal that should give you pause because a little rounding error can cause a problem.

Fortunately there is a much simpler way to calculate this condition: so long as the collision point's x coordinate is in between the two endpoints of the box edge, then you have an intersection:

  let minX = Math.min(l.p1[0], l.p2[0]);
  let maxX = Math.max(l.p1[0], l.p2[0]);
  if (x >= minX && x <= maxX) {
    // ...
  }

Here's a working example with some other tweaks to ease debugging:

let sides = 4; //no of sides of a regular polygon
let r; // distance of the vertices of the polygon from center
let points = []; //
let arrayofLines = [];
let arrayofBalls = [];
let noofBalls = 4;

function setup() {
  createCanvas(600, 600);
  angleMode(DEGREES);
  colorMode(HSB);
  r = width / 3;
  for (let i = 1; i <= sides; i++) {
    points.push(createRegularPoints(r, (i * 360) / sides));
  }
  // creating vertices of regular polygon
  createRegularPolygon(); //drawing regular polygon
  for (let i = 0; i < noofBalls; i++) {
    arrayofBalls.push(new ball()); // creating balls
  }
}

function draw() {
  background(20);
  stroke(0);
  strokeWeight(8);

  for (let i = 0; i < arrayofLines.length; i++) arrayofLines[i].show();
  for (let b of arrayofBalls) {
    b.move();
    b.show();
  }
}

function createRegularPoints(r, a) {
  return [width / 2 + r * cos(a), height / 2 + r * sin(a)];
}

function createRegularPolygon() {
  let i;
  for (i = 0; i < points.length - 1; i++)
    arrayofLines.push(
      new arrayofLineses(
        points[i][0],
        points[i][1],
        points[i + 1][0],
        points[i + 1][1]
      )
    ); //send x and y of i and i+1 point to create a line
  arrayofLines.push(
    new arrayofLineses(points[i][0], points[i][1], points[0][0], points[0][1])
  ); //last line is joint with the first line
}
class arrayofLineses {
  constructor(x1, y1, x2, y2) {
    this.p1 = [x1, y1];
    this.p2 = [x2, y2];
  }
  show() {
    push();
    stroke(400);
    strokeWeight(4);
    line(this.p1[0], this.p1[1], this.p2[0], this.p2[1]);
    pop();
  }
}
class ball {
  constructor() {
    // Generate only points inside the box
    let theta = random(0, 360);
    // Calculate the maximum distance from the center based on the polar equation for a square!
    let maxR = r * Math.min(1 / abs(sin(theta + 45)), 1 / abs(cos(theta + 45))) / sqrt(2);
    let offset = random(0, maxR);
    let pos = createVector(offset, 0).rotate(theta);
    this.x = pos.x + width / 2;
    this.y = pos.y + height / 2;

    // Pick a random direction, but a constant speed of 2
    let omega = random(0, 360);
    let vel = createVector(2, 0).rotate(omega);
    this.speedx = vel.x;
    this.speedy = vel.y;

    this.collisions = [];
  }

  show() {
    push();
    noStroke();
    fill('red');
    circle(this.x, this.y, 5);
    fill('lime');
    for (const [x, y] of this.collisions) {
      circle(x, y, 5);
    }
    pop();
  }

  move() {
    if (this.x <= 0 || this.x >= width) {
      this.speedx *= -1; //collision against vertical walls
    }
    if (this.y <= 0 || this.y >= height) {
      this.speedy *= -1; //collision against horizontal walls
    }

    let x1 = this.x; //these (x1,y1) and (x2,y2) now form a line in the direction of balls movment
    let y1 = this.y;
    let x2 = this.x + this.speedx;
    let y2 = this.y + this.speedy;

    for (let l of arrayofLines) {
      //checking collision against each line
      let x3 = l.p1[0]; //again two end points of the line
      let y3 = l.p1[1];
      let x4 = l.p2[0];
      let y4 = l.p2[1];

      //Here I calculate the intersection between two lines using two point form(formula from wiki). So x and y are collision coordinates
      let D = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
      let x =
        ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / D;
      let y =
        ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / D;

      // checking if the collision point x,y actually lies on the line segment
      /*
        dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
        dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1]) */
      // instead of summing the distance between the collision points and each
      // end point of the line, just see if it lies within the bounds of the
      // line in the x direction.
      let minX = Math.min(l.p1[0], l.p2[0]);
      let maxX = Math.max(l.p1[0], l.p2[0]);
      if (x >= minX && x <= maxX) {
        // This was for debugging
        // this.collisions.push([x, y]);
        //checking if the ball is close enough to line to be a called colliding
        let xdis = x - this.x;
        let ydis = y - this.y;
        if (xdis * xdis + ydis * ydis < 5 * 5) {
          //From here on, I used the mentioned pdf but doesn't use the vectors. Instead I create x and y for every vector mentioned.

          //normal vector (perpendicular to line, so slope of normal= -1/(slope of line))
          ydis = l.p1[0] - l.p2[0];
          xdis = -(l.p1[1] - l.p2[1]);

          let magn = sqrt(xdis * xdis + ydis * ydis);
          xdis /= magn;
          ydis /= magn;

          let utx = -ydis;
          let uty = xdis;
          let v1n = xdis * this.speedx + ydis * this.speedy;
          let v1t = utx * this.speedx + uty * this.speedy;
          v1n = -v1n;
          let v1nx = v1n * xdis;
          let v1ny = v1n * ydis;
          let v1tx = v1t * utx;
          let v1ty = v1t * uty;
          let vfx = v1nx + v1tx;
          let vfy = v1ny + v1ty;
          this.speedx = vfx;
          this.speedy = vfy;
        }
      }
    }

    this.x += this.speedx;
    this.y += this.speedy;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>