Search code examples
javascriptprocessingcollision-detectionphysicsp5.js

Corner Collision angles in p5.js


Summary:

In attempt to make a simple collision detection system between a moving rect and a falling circle, I would like to make it more realistic.

Main question:

-The main thing I would like to solve is detecting when the circle Object is hitting the corner of the rect and in return having the circle bounce based off of that angle.

The Code:

var balls = [];
var obstacle;

function setup() {
  createCanvas(400, 400);
  obstacle = new Obstacle();
}

function draw() {
  background(75);
  obstacle.display();
  obstacle.update();
  
  for (var i = 0; i < balls.length; i++) {
    balls[i].display();
	  if (!RectCircleColliding(balls[i], obstacle)){
        balls[i].update();
        balls[i].edges();
	  }
    
    //console.log(RectCircleColliding(balls[i], obstacle));
  }
}

function mousePressed() {
  balls.push(new Ball(mouseX, mouseY));
}

function Ball(x, y) {
  this.x = x;
  this.y = y;
  this.r = 15;
  this.gravity = 0.5;
  this.velocity = 0;
  this.display = function() {
    fill(255, 0, 100);
    stroke(255);
    ellipse(this.x, this.y, this.r * 2);
  }
  this.update = function() {
    this.velocity += this.gravity;
    this.y += this.velocity;
  }
  this.edges = function() {
    if (this.y >= height - this.r) {
      this.y = height - this.r;
      this.velocity = this.velocity * -1;
      this.gravity = this.gravity * 1.1;
    }
  }
}

function Obstacle() {
  this.x = width - width;
  this.y = height / 2;
  this.w = 200;
  this.h = 25;

  this.display = function() {
    fill(0);
    stroke(255);
    rect(this.x, this.y, this.w, this.h);
  }
  
  this.update = function() {
    this.x++;
    
    if (this.x > width + this.w /2) {
       this.x = -this.w;
    }
  }
}

function RectCircleColliding(Ball, Obstacle) {
  // define obstacle borders
  var oRight = Obstacle.x + Obstacle.w;
  var oLeft = Obstacle.x;
  var oTop = Obstacle.y;
  var oBottom = Obstacle.y + Obstacle.h;

  //compare ball's position (acounting for radius) with the obstacle's border
  if (Ball.x + Ball.r > oLeft) {
    if (Ball.x - Ball.r < oRight) {
      if (Ball.y + Ball.r > oTop) {
        if (Ball.y - Ball.r < oBottom) {
          
         let oldY = Ball.y;
         Ball.y = oTop - Ball.r;
         Ball.velocity = Ball.velocity * -1;
           if (Ball.gravity < 2.0){
              Ball.gravity = Ball.gravity * 1.1;  
           } else {
             Ball.velocity = 0;
             Ball.y = oldY;
         }   
         return (true);
        } 
      }
    }
  }
    return false;
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>

Expected output:

I would like to see the falling circles bounce off the rectangle with respect to where they are hitting on the rectangle.

If the circles hit the corners they should bounce differently versus hitting dead center.


Solution

  • I'll try to present a solution, which keeps as may from the original code as possible. The solution intends to be a evolution of the code presented in the question.

    Add a sideward movement (selv.sideV) to the Ball object, which is initialized by 0:

    function Ball(x, y) {
        this.x = x;
        this.y = y;
        this.r = 15;
        this.gravity = 0.5;
        this.velocity = 0;
        this.sideV = 0 
    
        // ...
    }
    

    Move the ball to the side in update, by the sideward movement and reduce the sideward movment:

    this.update = function() {
        this.velocity += this.gravity;
        this.y += this.velocity;
        this.x += this.sideV;
        this.sideV *= 0.98;
    }
    

    Create function for an intersection test of 2 boxes:

    function IsectRectRect(l1, r1, t1, b1, l2, r2, t2, b2) {
        return l1 < r2 && l2 < r1 && t1 < b2 && t2 < b1;
    }
    

    And a function which can calculate the refection vector R of an incident vector V to a the normal vector of surface N (the reflection like a billiard ball):

    function reflect( V, N ) {
        R = V.copy().sub(N.copy().mult(2.0 * V.dot(N)));
        return R;
    }
    

    When the Ball collides with the Obstacle, then you've to handle 3 situations.

    1. The Ball fully hits the top of the Obstacle: IsectRectRect(oL, oR, oT, oB, Ball.x, Ball.x, bT, bB)
    2. The Ball hits the left edge of the Obstacle: IsectRectRect(oL, oL, oT, oB, bL, bR, bT, bB)
    3. The Ball hits the right edge of the Obstacle: IsectRectRect(oR, oR, oT, oB, bL, bR, bT, bB)

    In each case the normal vector for the reflection has to be calculated. This is the vector from either the top or the edge of the Obstacle to the center of the Ball.
    Use the function reflect to bounce the Ball on the Obstacle:

    function RectCircleColliding(Ball, Obstacle) {
        let oL = Obstacle.x;
        let oR = Obstacle.x + Obstacle.w;
        let oT = Obstacle.y;
        let oB = Obstacle.y + Obstacle.h;
        let bL = Ball.x - Ball.r;
        let bR = Ball.x + Ball.r;
        let bT = Ball.y - Ball.r;
        let bB = Ball.y + Ball.r;
    
        let isect = false;
        let hitDir = createVector(0, 1);
        if ( IsectRectRect(oL, oR, oT, oB, Ball.x, Ball.x, bT, bB) ) {
            isect = true;
        } else if ( IsectRectRect(oL, oL, oT, oB, bL, bR, bT, bB) ) {
            hitDir = createVector(Ball.x, Ball.y).sub(createVector(oL, oT))
            isect = hitDir.mag() < Ball.r;
        } else if ( IsectRectRect(oR, oR, oT, oB, bL, bR, bT, bB) ) {
            hitDir = createVector(Ball.x, Ball.y).sub(createVector(oR, oT))
            isect = hitDir.mag() < Ball.r;
        }
    
        if ( isect ) {
            let dir = createVector(Ball.sideV, Ball.velocity);
            R = reflect(dir, hitDir.normalize());
            Ball.velocity = R.y;
            Ball.sideV = R.x;
            let oldY = Ball.y;
            Ball.y = oT - Ball.r;
            if (Ball.gravity < 2.0){
                Ball.gravity = Ball.gravity * 1.1;  
            } else {
                Ball.velocity = 0;
                Ball.y = oldY;
            }   
            return true;
        }
        return false;
    }
    

    See the example, where I applied the changes to your original code:

    var balls = [];
    var obstacle;
    
    function setup() {
      createCanvas(400, 400);
      obstacle = new Obstacle();
    }
    
    function draw() {
      background(75);
      obstacle.display();
      obstacle.update();
    
      for (var i = 0; i < balls.length; i++) {
        balls[i].display();
          if (!RectCircleColliding(balls[i], obstacle)){
            balls[i].update();
            balls[i].edges();
          }
    
        //console.log(RectCircleColliding(balls[i], obstacle));
      }
    }
    
    function mousePressed() {
      balls.push(new Ball(mouseX, mouseY));
    }
    
    function Ball(x, y) {
      this.x = x;
      this.y = y;
      this.r = 15;
      this.gravity = 0.5;
      this.velocity = 0;
      this.sideV = 0 
      this.display = function() {
        fill(255, 0, 100);
        stroke(255);
        ellipse(this.x, this.y, this.r * 2);
      }
      this.update = function() {
          this.velocity += this.gravity;
          this.y += this.velocity;
          this.x += this.sideV;
          this.sideV *= 0.98;
      }
      this.edges = function() {
        if (this.y >= height - this.r) {
          this.y = height - this.r;
          this.velocity = this.velocity * -1;
          this.gravity = this.gravity * 1.1;
        }
      }
    }
    
    function Obstacle() {
      this.x = width - width;
      this.y = height / 2;
      this.w = 200;
      this.h = 25;
    
      this.display = function() {
        fill(0);
        stroke(255);
        rect(this.x, this.y, this.w, this.h);
      }
    
      this.update = function() {
        this.x++;
    
        if (this.x > width + this.w /2) {
           this.x = -this.w;
        }
      }
    }
    
    function IsectRectRect(l1, r1, t1, b1, l2, r2, t2, b2) {
        return l1 < r2 && l2 < r1 && t1 < b2 && t2 < b1;
    }
    
    function reflect( V, N ) {
        R = V.copy().sub(N.copy().mult(2.0 * V.dot(N)));
        return R;
    }
    
    function RectCircleColliding(Ball, Obstacle) {
        let oL = Obstacle.x;
        let oR = Obstacle.x + Obstacle.w;
        let oT = Obstacle.y;
        let oB = Obstacle.y + Obstacle.h;
        let bL = Ball.x - Ball.r;
        let bR = Ball.x + Ball.r;
        let bT = Ball.y - Ball.r;
        let bB = Ball.y + Ball.r;
    
        let isect = false;
        let hitDir = createVector(0, 1);
        if ( IsectRectRect(oL, oR, oT, oB, Ball.x, Ball.x, bT, bB) ) {
            isect = true;
        } else if ( IsectRectRect(oL, oL, oT, oB, bL, bR, bT, bB) ) {
            hitDir = createVector(Ball.x, Ball.y).sub(createVector(oL, oT))
            isect = hitDir.mag() < Ball.r;
        } else if ( IsectRectRect(oR, oR, oT, oB, bL, bR, bT, bB) ) {
            hitDir = createVector(Ball.x, Ball.y).sub(createVector(oR, oT))
            isect = hitDir.mag() < Ball.r;
        }
    
        if ( isect ) {
            let dir = createVector(Ball.sideV, Ball.velocity);
            R = reflect(dir, hitDir.normalize());
            Ball.velocity = R.y;
            Ball.sideV = R.x;
            let oldY = Ball.y;
            Ball.y = oT - Ball.r;
            if (Ball.gravity < 2.0){
                Ball.gravity = Ball.gravity * 1.1;  
            } else {
                Ball.velocity = 0;
                Ball.y = oldY;
            }   
            return true;
        }
        return false;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>