Please note: this blog has been migrated to a new location at https://jakesgordon.com. All new writing will be published over there, existing content has been left here for reference, but will no longer be updated (as of Nov 2023)

Pong - Collision Detection

Sat, May 14, 2011

Ball Movement

In order to have some challenge, we need the ball to accelerate. Given a position, a speed, an acceleration and a time interval dt, the new position and speed of the ball can be calculated as follows:

accelerate: function(x, y, dx, dy, accel, dt) {
  var x2  = x + (dt * dx) + (accel * dt * dt * 0.5);
  var y2  = y + (dt * dy) + (accel * dt * dt * 0.5);
  var dx2 = dx + (accel * dt) * (dx > 0 ? 1 : -1);
  var dy2 = dy + (accel * dt) * (dy > 0 ? 1 : -1);
  return { nx: (x2-x), ny: (y2-y), x: x2, y: y2, dx: dx2, dy: dy2 };
},

Once we can accelerate, the ball’s update() method needs to:

update: function(dt, leftPaddle, rightPaddle) {

  pos = Pong.Helper.accelerate(this.x, this.y, this.dx, this.dy, this.accel, dt);

  if ((pos.dy > 0) && (pos.y > this.maxY)) {
    pos.y = this.maxY;
    pos.dy = -pos.dy;
  }
  else if ((pos.dy < 0) && (pos.y < this.minY)) {
    pos.y = this.minY;
    pos.dy = -pos.dy;
  }

  var paddle = (pos.dx < 0) ? leftPaddle : rightPaddle;
  var pt     = Pong.Helper.ballIntercept(this, paddle, pos.nx, pos.ny);

  if (pt) {
    switch(pt.d) {
      case 'left':
      case 'right':
        pos.x = pt.x;
        pos.dx = -pos.dx;
        break;
      case 'top':
      case 'bottom':
        pos.y = pt.y;
        pos.dy = -pos.dy;
        break;
    }

    // add/remove spin based on paddle direction
    if (paddle.up)
      pos.dy = pos.dy * (pos.dy < 0 ? 0.5 : 1.5);
    else if (paddle.down)
      pos.dy = pos.dy * (pos.dy > 0 ? 0.5 : 1.5);
  }

  this.setpos(pos.x,  pos.y);
  this.setdir(pos.dx, pos.dy);
},

Ball and Paddle Intersection

The ballIntercept() method used in the update() method above needs to accurately detect if the ball will intercept with the paddle during this time frame interval (dt).

The ball moves from p1 to p2 during the time interval (dt) and the paddle edge stretches from p3 to p4 and we need to see if these line segments intersect.

We want to be able to see if the ball intercepts the obvious side of the bat.

But we also want to check the non-obvious sides, such as the top and bottom to see if the ball ‘just missed’ the obvious edge but will bounce off the top or bottom before going out for a goal. Most pong games dont bother with this, but we are trying to be complete, so, assuming the existence of a simple line intercept() method, ballIntercept() can be implemented as follows:

ballIntercept: function(ball, rect, nx, ny) {
  var pt;
  if (nx < 0) {
    pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny, 
                               rect.right  + ball.radius, 
                               rect.top    - ball.radius, 
                               rect.right  + ball.radius, 
                               rect.bottom + ball.radius, 
                               "right");
  }
  else if (nx > 0) {
    pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny, 
                               rect.left   - ball.radius, 
                               rect.top    - ball.radius, 
                               rect.left   - ball.radius, 
                               rect.bottom + ball.radius,
                               "left");
  }
  if (!pt) {
    if (ny < 0) {
      pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny, 
                                 rect.left   - ball.radius, 
                                 rect.bottom + ball.radius, 
                                 rect.right  + ball.radius, 
                                 rect.bottom + ball.radius,
                                 "bottom");
    }
    else if (ny > 0) {
      pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny, 
                                 rect.left   - ball.radius, 
                                 rect.top    - ball.radius, 
                                 rect.right  + ball.radius, 
                                 rect.top    - ball.radius,
                                 "top");
    }
  }
  return pt;
}

Line Intersection

The ballIntercept() method relies on a generic line segment intersect method.

Assuming the ball moves from (x1,y1) to (x2,y2) and the paddle edge goes from (x3,y3) to (x4,y4), the intercept() method can be implemented as follows:

intercept: function(x1, y1, x2, y2, x3, y3, x4, y4, d) {
  var denom = ((y4-y3) * (x2-x1)) - ((x4-x3) * (y2-y1));
  if (denom != 0) {
    var ua = (((x4-x3) * (y1-y3)) - ((y4-y3) * (x1-x3))) / denom;
    if ((ua >= 0) && (ua <= 1)) {
      var ub = (((x2-x1) * (y1-y3)) - ((y2-y1) * (x1-x3))) / denom;
      if ((ub >= 0) && (ub <= 1)) {
        var x = x1 + (ua * (x2-x1));
        var y = y1 + (ua * (y2-y1));
        return { x: x, y: y, d: d};
      }
    }
  }
  return null;
},

The game is now playable as a 2 player game with this demo here

More…

You can find the final game here and the code is here