0

I'm a fairly new programmer, but I'm trying to create my own sort of physics engine without using any libraries. It's for an inter-planetary mini-golf game.

I've gotten the gravity working fine, based on the density and location of the planets, and my collision detection works properly, but what to do when they collide is what I cannot solve.

The planets won't move, but using Javascript to do the trigonometry required to calculate the resulting velocity is proving very difficult.

Here is my physics code, being executed with requestAnimationFrame:

  var a = [0, 0];
  // p is in the format [x, y, radius, density]
  planets.forEach(function (p) {
    var d = Math.sqrt((p[0] - ball.coor[0]) ** 2 + (p[1] - ball.coor[1]) ** 2);
    var m = p[3] * 4 / 3 * Math.PI * p[2] ** 3;
    var f = G * m / d ** 2;
    var angle = Math.atan2(p[1] - ball.coor[1], p[0] - ball.coor[0]);
    a[0] += f * Math.cos(angle);
    a[1] += f * Math.sin(angle);
    if (d < p[2] + ball.radius) {
      var impulse = [0, 0];
      // What to do here?
      // This is the closest I got:
      var velocitya = Math.atan(b.v[1] / b.v[0]);
      var trianglea = Math.abs(velocitya - angle);
      var currV = Math.sqrt(b.v[0] ** 2 + b.v[1] ** 2);
      var newV = currV * bounciness;
      var newa = (Math.PI / 2 - trianglea) * 2 + velocitya;
      b.v[0] = newV * Math.cos(newa);
      b.v[1] = newV * Math.sin(newa);
      // Somehow I just can't get it right.
      // It's the JavaScript, not the math concepts.
    }
  });

  ball.v[0] += a[0];
  ball.v[1] += a[1];
  ball.coor[0] += ball.v[0];
  ball.coor[1] += ball.v[1];

ball is just the object for the golf ball.

I've tried various methods, but I just cannot get the collisions to work properly. It seems working with the directions and angles is giving me grief. I've done a lot of research, but nothing I've found seems to be correctly solving my problem.

So here's my question: How can I calculate the impulse using vanilla JavaScript?

Note: The planets don't move, and the bounciness should be a variable.

Josiah Plett
  • 180
  • 14
  • What is the variable `lev`? – Willis Blackburn Jun 21 '19 at 00:12
  • I've edited it. It's just the mini-golf level. I meant to remove it for simplicity. :) – Josiah Plett Jun 21 '19 at 00:25
  • It looks like you're accumulating a gravitational acceleration in `a`. Are you trying to make the ball elastically bounce off any planet that it touches? – Willis Blackburn Jun 21 '19 at 00:29
  • Yes. My collision detection works, but I can't seem to get any impulse happening properly. – Josiah Plett Jun 21 '19 at 00:32
  • Are you not sure of the math or the programming? I think that you want to consider the ball to be bouncing off a line that's tangential to the collision point. In other words its velocity will be reversed and reflected across the line separating the two centers. – Willis Blackburn Jun 21 '19 at 00:35
  • Sorry, I should have been more clear. I know the math, but converting it to JavaScript is not working out for me. – Josiah Plett Jun 21 '19 at 00:39
  • Okay, the first issue is, what happens when `b.v[0]` is zero? – Willis Blackburn Jun 21 '19 at 00:45
  • I'm not sure how to handle that issue. I looked at [this article](https://stackoverflow.com/questions/8072323/best-way-to-prevent-handle-divide-by-0-in-javascript) but I couldn't implement it properly. – Josiah Plett Jun 21 '19 at 00:49
  • Well you don't want `b.v[1] / b.v[0]`, you want `Math.arctan(b.v[1] / b.v[0])`. So if `b.v[0]` is zero, just use the appropriate angle value (PI/2?) instead. – Willis Blackburn Jun 21 '19 at 01:05
  • And ditto for `angle`. – Willis Blackburn Jun 21 '19 at 01:06
  • Math.arctan is not a function, I think. Anyway, I've fixed that problem now, but it certainly hasn't solved the whole collision issue. Do you have any recommendations? – Josiah Plett Jun 21 '19 at 01:11
  • Yes it's `Math.atan`, but you already knew that. The call to `Math.abs` looks suspicious too. It seems like `velocitya` could legitimately be greater than or less than `angle` and you want to generate different outputs in each case. – Willis Blackburn Jun 21 '19 at 02:09
  • Sorry but I tried that already and it made the whole thing worse. Looks like this isn't really going anywhere, so if you want you can delete this question. (I don't have enough reputation) – Josiah Plett Jun 21 '19 at 02:39
  • I think I'll answer it instead. – Willis Blackburn Jun 21 '19 at 02:54
  • by impulse, do you mean preventing the golf ball from entering inside the planet? – Joshua Usi Jun 21 '19 at 03:48

2 Answers2

1

Okay, I think this works.

Given:

  • Planet position as {x,y}
  • Ball position as {x,y}
  • Ball velocity as {dx,dy}

I think the steps are as follows:

  1. Calculate the angle between the x axis and a line between the centers of the planet and ball.
  2. Rotate the velocity vector clockwise through this angle, in other words, rotate it by the negative angle. Now we have the velocity vector as it would be if the planet and ball were aligned on the x axis.
  3. Now, to "bounce" off the planet, we only need to invert the x component of the velocity.
  4. Rotate the new velocity counter-clockwise through the angle.

It seems to work. For clarity let's assume that b (ball), p (planet), and v (velocity) are objects with x and y members.

function impact(b, p, v) {
    let a = b.x == p.x
            ? Math.PI / 2
            : Math.atan((b.y - p.y) / (b.x - p.x));
    v = rotate(v, -a);
    v.x = -v.x;
    return rotate(v, a);
}

function rotate(p, a) {
    return {
        x: p.x * Math.cos(a) - p.y * Math.sin(a),
        y: p.y * Math.cos(a) + p.x * Math.sin(a)
    );
}

You'll have to do a little work to convert this into your style using arrays etc.

If you want to slow down the ball a bit you can probably figure out how to do that too; easiest is to just scale the x and y components of the vector down by some factor.

Willis Blackburn
  • 8,068
  • 19
  • 36
1

Could something like this work? (double check for syntax errors)

var a = [0, 0];
// p is in the format [x, y, radius, density]
planets.forEach(function (p) {
    var d = Math.sqrt((p[0] - ball.coor[0]) ** 2 + (p[1] - ball.coor[1]) ** 2);
    var r = [0, 0]; //radial vector of length one from ball's center to planet's center
    r[0] = (p[0] - ball.coor[0]) / d; // this is the same as cos(angle)
    r[1] = (p[1] - ball.coor[1]) / d; // this is the same as sin(angle)
    // I removed your implementation, it was using redundant expensive calculations
    var m = p[3] * 4 / 3 * Math.PI * p[2] ** 3;
    var f = G * m / d ** 2;
    a[0] = a[0] + f * r[0];
    a[1] = a[1] + f * r[1];
    if (d < p[2] + ball.radius) {
      var dot_v_r = ball.v[0] * r[0] + ball.v[1] * r[1];
      ball.v[0] = bounciness * (ball.v[0] - 2 * dot_v_r * r[0]);
      ball.v[1] = bounciness * (ball.v[1] - 2 * dot_v_r * r[1]);
      // this is complete elastic reflection of the ball, leaving the planet stationary
      // then velocity's magnitude (but not direction) is corrected with bounciness 
    }
});

ball.v[0] = ball.v[0] + time_step * a[0];
ball.v[1] = ball.v[1] + time_step * a[1];
ball.coor[0] = ball.coor[0] + time_step * ball.v[0];
ball.coor[1] = ball.coor[1] + time_step * ball.v[1];

//time_step is a time step, i.e. how frequently you want to update ball's position and velocity
//you can choose a value that makes the update of frames appropriate.  
Futurologist
  • 1,874
  • 2
  • 7
  • 9