0

I'm developing a physics-engine in Python (I know, it's mostly for "learning") and have it come along quite well. I've been following a lot of online documentation on the matter, and this series of articles was especially helpful: How to Create a Custom Physics Engine.

I've got materials working, that act as a class that holds restitution and density of a body. What happens next is that bodies that have a low density and a high restitution (e.g. 0.3, 0.8 respectively), they bounce off each other with such high velocity it doesn't take long for them to shoot off into the "void".

I have a test scene set up with 4 walls that encloses the bodies (ball). When they go at such high velocity, a collision is never registered as the next physics simulation happens after they already passed the "walls".

What I'm really interested in is why they get such high velocity when bouncing off each other, while I don't believe they should. In my example recording, I have a small ball (red) which is controller by the player (me). It has a density of 0.3 and a restitution of 0.8, with a volume of 100 (r * r or 10 * 10), its mass is 30 KGs (100 * 0.3). Another larger ball (green) is not controller by the player, but can be bounced by hitting it with the red ball. It has the same material, therefor has the same density and restitution. However, its mass is higher, because of the higher radius (30). The actual mass of the green ball is 270 (900 * 0.8).

In the recording it's noticeable the balls bounce off of each other in an unnatural way. Shouldn't the small ball get inflicted a higher impulse than the big ball? I tried to work around this by swapping the mass values used when calculating the impulse. So the small ball's impulse is calculated using the mass of the big ball, and vice-versa. You'd think this is due to the bigger ball having a higher mass, but to me they bounce off of each other rather quickly?

Is this normal behaviour for the density and restitution I have on both of these balls? I will share code if needed, but as this text is already getting a bit long, I won't waste space if not needed. My code is almost exactly following what is covered in the articles, only "converted" into Python code.

Bouncing Balls (Red = Player)Bouncing Balls (Red = Player)

Code on calculating impulse

# Relative velocity
rv = b.velocity - a.velocity

# Velocity along the normal direction
vel_along_normal = rv.dot(contact_normal)

# EPSILON (use lowest restitution value)
e = min(a.restitution, b.restitution)

# Calculate j, which will be used to get the impulse
j = -(1 + e) * vel_along_normal
j /= a_inv_mass + b_inv_mass

impulse = contact_normal * j

# Using ratio to have lighter bodies bounce with a higher impulse than heavier bodies
mass_sum = a_mass + b_mass
ratio = (b_mass / mass_sum)
a.rigid_body.velocity -= (impulse * ratio) * dt

ratio = (a_mass / mass_sum)
b.rigid_body.velocity += (impulse * ratio) * dt
Qlii256
  • 460
  • 6
  • 11
  • Restitution `e` is a value 0 to 1. Assuming e is a positive value after `e = min(a.restitution, b.restitution)` then the next line `j = -(1 + e) * vel_along_normal`is incorrect as it makes `(1 + e) > 1` which will add energy to the collision (which should never happen in the real world). As it is unclear how you define `e` I would assume the standard definition and say that the line calculating `j` should be `j = -e * vel_along_normal` – Blindman67 Jul 27 '20 at 07:59
  • When changing the code like this, the ball just smashes against a collider and does nothing. A restitution value is 0 to 1, as you stated. The ball has a `e` of 0.8, so it should bounce quite high. But it does not. – Qlii256 Jul 27 '20 at 12:50
  • I managed to get it working, I was not multiplying by dt. They still behave weirdly though. With a stationary object, it seems to work fine, but as soon as both are moving, they suddenly start to go super fast. – Qlii256 Jul 27 '20 at 13:03
  • It seems like the heavier the body, the faster it goes but only against stationary objects. I believe it's because stationary objects have a mass of 0 (infinite mass). So `j` is not properly calculated because of the 0 inverse_mass. What value should I use if one of the bodies has infinite mass? – Qlii256 Jul 27 '20 at 13:09
  • Now that I think of it, when a wall has infinite mass, isn't it supposed to send back a high impulse just like we see happening? So, I guess the reason why there's kinematics is to solve this issue. So you can still have a mass, but keep the body from being affected by forces. – Qlii256 Jul 27 '20 at 13:20

2 Answers2

0

It seems that there is a sign problem with velocity difference after collision. Rather than slowing red ball down it speeds it up.

Aramakus
  • 1,910
  • 2
  • 11
  • 22
  • Any idea what this could be? I can share the code that calculates the impulse. I've also added another video where you can see this "weird" behaviour with balls of equal sizes and mass. At first they behave "correctly", but soon they start going at such high velocities. – Qlii256 Jul 26 '20 at 09:18
  • Check the maths that in part of the code that update velocities correctly after collision. – Aramakus Jul 26 '20 at 09:21
  • I've added some sample code which almost directly represents my actual code. You can notice that I swapped the values that I add to the bodies. Body a gets the mass from b and vice-versa, is this supposed to be like this? – Qlii256 Jul 26 '20 at 09:34
0

The whole function does not look right to me, and as it does not seam to work as expected there is something wrong. Rather than try and workout what is wrong (too many unknowns in your code, dt, a_inv_mass, b_inv_mass, contact_normal) the following snippet will do the collision math for you

// A and B are balls at time of collision
mA = A.mass
mB = B.mass
mm = mA + mB

// vector between balls
x = A.x - B.x
y = A.y - B.y  

// restitution
e1 = A.restitution
e2 = B.restitution

// distance sqr between balls
d = x * x + y * y;

// v velocities
uAB = (A.v.x * x + A.v.y * y) / d  // normal component force A into B
u2  = (A.v.y * x - A.v.x * y) / d  // tangent component force A

uBA = (B.v.x * x + B.v.y * y) / d  // normal component force B into A
u4  = (B.v.y * x - B.v.x * y) / d  // tangent component force B

// Force from B into A along normal, scaled for mass ratio and restitution
uA = ((mA - mB) / mm * uAB + (2 * mB) / mm * uBA) * e1

// Force from A into B along normal, scaled for mass ratio and restitution
uB = ((mB - mA) / mm * uBA + (2 * mA) / mm * uAB) * e2

// new velocities 
A.v.x = x * uA - y * u2
A.v.y = y * uA + x * u2

B.v.x = x * uB - y * u4
B.v.y = y * uB + x * u4

Note that dt is ignored (assuming dt to be delta time)

Note both objects must have some mass. This will not work if one or both have 0 mass.

Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • Must dt be ignore? I thought a change in velocity needed the dt value to be applied. The `inv_mass` variables are the inverse of the mass `(1 / mass)` as stated in the article. As multiplication is much cheaper to do then divisions. And by storing the inverse mass, it is cheaper to use that instead. – Qlii256 Jul 27 '20 at 13:45
  • @Qlii256 dt is only applied to the position eg `A.x += A.v.x * dt` There is room for lots of optimization in the above code however I don't see how `1/mass` will save time as it is the ratio of mass that matters, and each collision will have a different ratio. You could do `mm = 1 / (mA +mB)` and then `uA = ((mA - mB) * uAB + 2 * mB * uBA) * mm * e1` and same for `uB` – Blindman67 Jul 27 '20 at 14:00
  • Ok, so what about infinite mass (value of 0). What is expected to happen if one of the bodies has infinite mass? – Qlii256 Jul 27 '20 at 14:22
  • @Qlii256 Don't you mean no mass (value of 0). If one ball has mass of 0 the result of a collision is undefined (meaningless). If one ball has infinite mass then the other will gain infinite velocity (as it is an elastic collision), unless `e == 0` – Blindman67 Jul 27 '20 at 14:43
  • @Qlii256 The code in my answer is derived from wiki https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional – Blindman67 Jul 27 '20 at 14:57
  • I really appreciate your help. I'm trying to get this code implemented and I got it somewhat working. But when going straight down, it always bounces of towards the right. I'm not sure what I'm doing wrong. – Qlii256 Jul 27 '20 at 16:41
  • @Qlii256 I have double checked my answer and can not see any typos of mistakes. This link https://stackoverflow.com/a/62851935/3877726 has a working JS example at bottom. The function called `Ball.collide`, the only difference is that `time` defines the unit time of the collision between frames which is assumed 0 in this answer – Blindman67 Jul 27 '20 at 17:25
  • I did find why it was going the wrong way. I was using positions instead of velocity for variables u. But another problem I'm now facing is that it seems to work as long as I hit the ball at the center position of the other rectangle. If I go past it the ball goes through and all kind of weird things start to happen... The positions (A.x, B.x etc), are those the center positions of the balls and rectangles or the position of contact? – Qlii256 Jul 27 '20 at 17:33
  • @Qlii256 A.x, A.y and same for B are ball centers. The balls should never overlap, they should be at least `A.radius + B.radius` apart along the line between them at collision. You must also consider that each ball moves between frames. A full collision resolution requires that all collision are resolved in chronological order between frames. The example snippet in answer I linked to shows how this can be done. – Blindman67 Jul 27 '20 at 19:14
  • I notice some circular behaviour. I can clearly see it's going through the rectangle and following a path along the center position of the rectangle. Is this only intended for circles (balls)? I can make a GIF if needed. Testing this with 2 circles seems to be working fine! – Qlii256 Jul 27 '20 at 19:40
  • The way I have it set up is I detect a collision and the function that does the impulse resolution receives the contact normal (the opposite direction of the body B on the side of the hit) and the contact penetration (the amount of overlap between the two bodies, which should always be minor). – Qlii256 Jul 28 '20 at 09:16