Detect impact force in Phaser with P2JS - phaser-framework

When running a Phaser with the P2JS Physics engine, I know that it's possible to detect collisions with Phaser.Physics.P2.Body#onBeginContact. But is there a way to test the impact force of the collision, so that I can apply a realistic level of damage to my spaceship when it collides with an asteroid?

You can determine the momentum of the impact using Phaser.Physics.P2.Body#onBeginContact like so:
Create your ship and add the onBeginContact signal handler to it's P2 body
ship.body.onBeginContact.add(contactHandler);
The contactHandler event will be sent 4 parameters: The body it is in contact with, the shape from this body that caused the contact, the shape from the contact body and the contact equation data array.
function contactHandler(body, shape1, shape2, equation) {
//get the velocity of asteroid at point of contact
var v = body.velocity.y;
//and the asteroid's mass
var m = body.mass;
//calculate momentum
var momentum = m*v;
//momentum affects ship damage
ship.damage(momentum);
}
Note in this example that I'm just using the vertical velocity of the asteroid to calculate the momentum. For a more accurate calculation use both the ship's and asteroid's velocities... something like this:
var v1 = new Phaser.Point(ship.velocity.x, ship.velocity.y);
var v2 = new Phaser.Point(body.velocity.x, body.velocity.y);
// calculate difference
var v = Math.abs( v1 - v2 );
Here's the code in action:
http://jsfiddle.net/bstevens/a6paycw6/

Well the easiest way I could think of off the top of my head would be to take the asteroid objects velocity into account. So if the asteroid is coming down on the ship from the top of the screen and the asteroids y-velocity is say greater than 200 add more damage then you would add for the y-velocity being say 100. I'm not sure what the actual numbers in your game are but I think the concept of using the velocity of the asteroid still stands.
--Edit--
Say you have a spaceship and an asteroid and they have collided (vertically with spaceship towards bottom of screen) and now you are in a method that will reduce the health of the spaceship based on how hard the collision was the if statement would look something like this:
if(asteroid.body.velocity.y<100)
spaceship.health-=10;
else if(asteroid.body.velocity.y<200)
spaceship.health-=20;
else
spaceship.health-=30;
(Also to make the numbers a bit more realistic I changed the y velocity values. I realize now that a y-velocity of 20 is actually quite slow and 100-300 makes a bit more sense)

Related

Get Rigidbody2D collision velocity without the sliding factor

I have a flying car that I want to lose some HP when colliding with objects.
I connected car's RigidBody2D to this function to do that
func _on_Car_body_entered(body):
var force = linear_velocity.length()
var dmg = pow(force / 100, 2) - 0.25
if dmg <= 0: return
Health = Health - dmg
Now, since I don't have to be precise I'm just using current velocity as the force, though this is up for change.
After getting my 'force of impact', I put it into damage calculating formula and if damage is above 0, decrease HP by damage.
This works fine in most cases
BUT
I noticed that if car's going fast horizontally and just barely touch the ground (that's perfectly horizontal), car gets hit with a lot of damage, because I'm using the length of the velocity vector.
Ofcourse, this case can be managed by using just the Y component of the velocity vector, but then it removes any horizontal collisions, and vice versa, and it also leads me on to the path of programming vertical and horizontal collisions, and ofcourse those are not the only 2 directions of colisions I need.
Is there a way to remove the sliding factor from this equation?
You can get the sine of the angle between your velocity and the collision normal, and then take the absolute of that.
# 0 When sliding along the wall. 1 when hitting the wall head on
var slide_factor = abs(cos(vel_last_frame.angle_to(collision_normal)))
This will give you a value from 0 to 1. When you are just sliding along the wall, this value will be 0, and when you hit the wall straight on, it will be 1.
I am using the velocity from the last frame here so that it gets the velocity just before the collision. I get it by setting vel_last_frame to linear_velocity inside the _physics_process function.
You can only get the collision normal inside the _integrate_forces function using PhysicsDirectBodyState.get_local_contact_normal(), so you need to make a variable that can be accessed in this function and the _on_Car_body_entered function. Note that you need to set contact_monitor to true and contacts_reported to at least 1 for this function to work.
var collision_normal
func _integrate_forces(state):
# Check if there is a collision
if state.get_contact_count():
# contact_monitor must be true and contacts_reported must be at least 1 for this to work
collision_normal = state.get_contact_local_normal(0)
Now in the _on_Car_body_entered_function, you can multiply dmg by sliding_factor to scale it less depending on how much you are sliding against the wall.
func _on_Car_body_entered(body):
var force = linear_velocity.length()
# 0 When sliding along the wall. 1 when hitting the wall head on
var slide_factor = abs(cos(vel_last_frame.angle_to(collision_normal)))
var dmg = pow(force / 100, 2) - 0.25
# Reduce dmg depending on how much you are sliding against the wall
dmg *= slide_factor
if dmg <= 0: return
Health = Health - dmg
Found a solution for my problem here
This gives me a predictable force range to work with.
I copied all code for 2D collision, just added damage calculation
Range of forces my objects produce is <3000 for small collisions like scratches and bumps, ~10k for beginner friendly damage, and 20k+ for when I really slam the gas pedal, so I just convert that force to damage that I want.
Best part is that I don't have to use the body_entered from RigidBody2D, because now all my cars have this calculation in them, so when 2 of them collide they both get damaged.
extends RigidBody2D
var collision_force : Vector2 = Vector2.ZERO
var previous_linear_velocity : Vector2 = Vector2.ZERO
func _integrate_forces(state : Physics2DDirectBodyState)->void:
collision_force = Vector2.ZERO
if state.get_contact_count() > 0:
var dv : Vector2 = state.linear_velocity - previous_linear_velocity
collision_force = dv / (state.inverse_mass * state.step)
var dmg = collision_force.length() / 2000 - 2
if dmg > 0:
set_hp(Health - dmg)
emit_signal("Damaged")
previous_linear_velocity = state.linear_velocity
**OLD ANSWER**
RUBBER DUCK HERE
In script for car I added a new variable var last_linear_velocity = Vector2()
Then stored the last velocity in _process
func _process(delta):
last_linear_velocity = linear_velocity
Not in _integrate_forces because if I put it there then the new and last velocities are the same.
And just changed how force is calculated in the function mentioned above, so it looks like this
func _on_Car_body_entered(body):
var force = last_linear_velocity.length() - linear_velocity.length()
var dmg = pow(force / 100, 2) - 0.25
if dmg <= 0: return
Health = Health - dmg
Now I get a nice predicable range of values and can transform that to damage.
NOTE
I noticed that sometimes when collision occures the difference between the last and current velocity lengths is negative, as in - car is accelerating.
Anyway, this works for me for now.
If you find a better solutions do post it, as I couldn't find a solution to this problem online elswhere.

Ultimate struggle with a full 3d space controller

Sorry if i'm stupid or something, but i having a deep dread from a work on a "full 3d" space movement.
I'm trying to make a "space ship" KinematicBody controller which using basis vectors as a rotation point and have ability to strafe/move left,right,up,down based on it's facing direction.
The issue is i'm having that i want to use a Vector3 as a storage of all input variables, an input strength in particular, but i can't find a convenient way to orient or use this vector's variables to apply it to velocity.
I got a sort of cheap solution which i don't like with applying a rotation to an input vector so it will "corresponds" to one of the basis, but it's starting to brake at some angels.
Could please somebody suggest what i can change in my logic or maybe there is a way to
use quaternion/matrix related methods/formulas?
I'm not sure I fully understand what you want to do, but I can give you something to work with.
I'll assume that you already have the input as a Vector3. If not, you want to see Input.get_action_strength, Input.get_axis and Input.get_vector.
I'm also assuming that the braking situations you encountered are a case of gimbal lock. But since you are asking about applying velocity not rotation, I'll not go into that topic.
Since you are using a KinematicBody, I suppose you would be using move_and_slide or similar method, which work in global space. But you want the input to have to be based on the current orientation. Thus, you would consider your Vector3 which represents the input to be in local space. And the issue is how to go from that local space to the global space that move_and_slide et.al. need.
Transform
You might be familiar with to_local and to_global. Which would interpret the Vector3 as a position:
var global_input_vector:Vector3 = to_global(input_vector)
And the opposite operation would be:
input_vector = to_local(global_input_vector)
The problem with these is that since these consider the Vector3 to be positions, they will translate the vector depending where the KinematicBody is. We can undo that translation:
var global_vec:Vector3 = to_global(local_vec) - global_transform.orign
And the opposite operation would be:
local_vec = to_local(global_vec + global_transform.orign)
By the way this is another way to write the same code:
var global_vec:Vector3 = (global_transform * local_vec) - global_transform.orign
And the opposite operation would be:
local_vec = global_transform.affine_inverse() * (global_vec + global_transform.orign)
Which I'm mentioning because I want you to see the similarity with the following approach.
Basis
I would rather not consider the Vector3 to be positions. Just free vectors. So, we would transform it with only the Basis, like this:
var global_vec:Vector3 = global_transform.basis * local_vec
And the opposite operation would be:
local_vec = global_transform.affine_inverse().basis * global_vec
This approach will not have the translation problem.
You can think of the Basis as a 3 by 3 matrix, and the Transform is that same matrix augmented with a translation vector (origin).
Quat
However, if you only want rotation, let us se quaternions instead:
var global_vec:Vector3 = global_transform.basis.get_rotation_quat() * local_vec
And the opposite operation would be:
local_vec = global_transform.affine_inverse().basis.get_rotation_quat() * global_vec
Well, actually, let us invert just the quaternion:
local_vec = global_transform.basis.get_rotation_quat().inverse() * global_vec
These will only rotate the vector (no scaling, or any other transformation, just rotation) according to the current orientation of the KinematicBody.
Rotating a Transform
If you are trying to rotate a Transform, either…
Do this (quaternion):
transform = Transform(transform.basis * Basis(quaternion), transform.origin)
Or this (quaternion):
transform = transform * Transform(Basis(quaternion), Vector3.ZERO)
Or this (axis-angle):
transform = Transform(transform.basis.rotated(axis, angle), transform.origin)
Or this (axis-angle):
transform = transform * Transform.Identity.rotated(axis, angle)
Or this (Euler angles):
transform = Transform(transform.basis * Basis(pitch, yaw, roll), transform.origin)
Or this (Euler angles):
transform = transform * Transform(Basis(pitch, yaw, roll), Vector3.ZERO)
Avoid this:
transform = transform.rotated(axis, angle)
The reason is that this rotation is always before translation (i.e. this rotates around the global origin instead of the current position), and you will end up with an undesirable result.
And yes, you could use rotate_x, rotate_y and rotate_z, or set rotation of a Spatial. But sometimes you need to work with a Transform directly.
See also:
Godot/Gdscript rotate + translate from local to world space.
How to LERP between 2 angles going the longest route or path in Godot.

Play sound in Godot when a kinematicbody2D collides with a tile map?

The player in my game is a kinematicbody2D, and I want a sound effect to play whenever it collides with the tile map. My player uses move_and_slide() to collide with the tile map.
How can I trigger a sound to play when the player hits the tilemap? I only want the sound to play after the player initially collides with the tile map; it shouldn’t play over and over if the player is sitting on the floor.
I know how to use sounds with the StreamAudioPlayer, I just need help triggering the sound to play when I want it to play.
The simpler way to keep track of is_on_floor before and after move_and_slide.
For example:
var was_on_floor = is_on_floor()
move_and_slide(velocity, Vector2.UP)
if is_on_floor() and not was_on_floor:
$sound.play()
Whenever you call move_and_slide Godot updates the values you get from is_on_floor, is_on_wall and is_on_ceiling. To know what is wall, floor or ceiling, Godot uses the vector you pass as second parameter (if you pass Vector2.ZERO then everything is wall).
If you only care to check if it collided with something at all, you may use get_slide_count after move_and_slide. The function get_slide_count should give you 0 if there were no collisions:
if get_slide_count() > 0:
$sound.play()
Or you could check if the velocity that move_and_slide returns a different velocity that the one you passed. Meaning it did collide (or slide).
var old_velocity = velocity
velocity = move_and_slide(velocity, Vector2.UP)
if velocity != old_velocity:
$sound.play()
Alternatively you may use get_slide_collision, which gives you a KinematicCollision2D object:
for i in get_slide_count():
var collision = get_slide_collision(i)
print("Collided with: ", collision.collider.name)
Which will let you identify with what did the KinematicBody2D collide with, and then you could pick a sound accordingly.

Ray Trace Refraction looks "fake"

So in my ray tracer that I'm building I've gotten refraction to work for spheres along with caustic effects, however the glass spheres don't look particularly good. I believe the refracted math is correct because the light appears to be bending in inverting the way you'd expect it to however it doesn't look like glass, it just looks like paper or something.
I've read that total internal reflection is responsible for much of what makes glass look like it does, however when I tested to see if any of my refracted rays go above the critical angle, none of them do, so my glass sphere has no total internal reflection. I'm not sure if this is normal or if I've done something wrong. I've posted my refraction code below so if anyone has any suggestions I'd love to hear them.
/*
* Parameter 'dir' lets you know whether the ray is starting
* from outside the sphere going in (0) or from in the sphere
* going back out (1).
*/
void Ray::Refract(Intersection *hit, int dir)
{
float n1, n2;
if(dir == 0){ n1 = 1.0; n2 = hit->mat->GetRefract(); }
if(dir == 1){ n1 = hit->mat->GetRefract(); n2 = 1.0; }
STVector3 N = hit->normal/hit->normal.Length();
if(dir == 1) N = -N;
STVector3 V = D/D.Length();
double c1 = -STVector3::Dot(N, V);
double n = n1/n2;
double c2 = sqrt(1.0f - (n*n)*(1.0f - (c1*c1)));
STVector3 Rr = (n * V) + (n * c1 - c2) * N;
/*These are the parameters of the current ray being updated*/
E = hit->point; //Starting point
D = Rr; //Direction
}
This method is called during my main ray-tracing method RayTrace() which runs recursively. Here's a small section of it below that is in charge of refraction:
if (hit->mat->IsRefractive())
{
temp.Refract(hit, dir); //Temp is my ray that is being refracted
dir++;
result += RayTrace(temp, dir); //Result is the return RGB value.
}
You're right, you'll never get total internal reflection in a sphere (seen from the outside). That's because of symmetry: a ray inside a sphere will hit the surface at the same angle at both ends, meaning that, if it exceeds the critical angle at one end, it would have to exceed it at the other one as well (and so would not have been able to enter the sphere from the outside in the first place).
However, you'll still get a lot of partial reflection according to Fresnel's law. It doesn't look like your code accounts for that, which is probably why your glass looks fake. Try including that and see if it helps.
(Yes, it means that your rays will split in two whenever they hit a refractive surface. That happens in reality, so you'll just have to live with it. You can either trace both paths, or, if you're using a randomized ray tracing algorithm anyway, just sample one of them randomly with the appropriate weights.)
Ps. If you don't want to deal with stuff like light polarization, you may want to just use Schlick's approximation to the Fresnel equations.

Ball to Ball Collision - Detection and Handling

With the help of the Stack Overflow community I've written a pretty basic-but fun physics simulator.
You click and drag the mouse to launch a ball. It will bounce around and eventually stop on the "floor".
My next big feature I want to add in is ball to ball collision. The ball's movement is broken up into a x and y speed vector. I have gravity (small reduction of the y vector each step), I have friction (small reduction of both vectors each collision with a wall). The balls honestly move around in a surprisingly realistic way.
I guess my question has two parts:
What is the best method to detect ball to ball collision?
Do I just have an O(n^2) loop that iterates over each ball and checks every other ball to see if it's radius overlaps?
What equations do I use to handle the ball to ball collisions? Physics 101
How does it effect the two balls speed x/y vectors? What is the resulting direction the two balls head off in? How do I apply this to each ball?
Handling the collision detection of the "walls" and the resulting vector changes were easy but I see more complications with ball-ball collisions. With walls I simply had to take the negative of the appropriate x or y vector and off it would go in the correct direction. With balls I don't think it is that way.
Some quick clarifications: for simplicity I'm ok with a perfectly elastic collision for now, also all my balls have the same mass right now, but I might change that in the future.
Edit: Resources I have found useful
2d Ball physics with vectors: 2-Dimensional Collisions Without Trigonometry.pdf
2d Ball collision detection example: Adding Collision Detection
Success!
I have the ball collision detection and response working great!
Relevant code:
Collision Detection:
for (int i = 0; i < ballCount; i++)
{
for (int j = i + 1; j < ballCount; j++)
{
if (balls[i].colliding(balls[j]))
{
balls[i].resolveCollision(balls[j]);
}
}
}
This will check for collisions between every ball but skip redundant checks (if you have to check if ball 1 collides with ball 2 then you don't need to check if ball 2 collides with ball 1. Also, it skips checking for collisions with itself).
Then, in my ball class I have my colliding() and resolveCollision() methods:
public boolean colliding(Ball ball)
{
float xd = position.getX() - ball.position.getX();
float yd = position.getY() - ball.position.getY();
float sumRadius = getRadius() + ball.getRadius();
float sqrRadius = sumRadius * sumRadius;
float distSqr = (xd * xd) + (yd * yd);
if (distSqr <= sqrRadius)
{
return true;
}
return false;
}
public void resolveCollision(Ball ball)
{
// get the mtd
Vector2d delta = (position.subtract(ball.position));
float d = delta.getLength();
// minimum translation distance to push balls apart after intersecting
Vector2d mtd = delta.multiply(((getRadius() + ball.getRadius())-d)/d);
// resolve intersection --
// inverse mass quantities
float im1 = 1 / getMass();
float im2 = 1 / ball.getMass();
// push-pull them apart based off their mass
position = position.add(mtd.multiply(im1 / (im1 + im2)));
ball.position = ball.position.subtract(mtd.multiply(im2 / (im1 + im2)));
// impact speed
Vector2d v = (this.velocity.subtract(ball.velocity));
float vn = v.dot(mtd.normalize());
// sphere intersecting but moving away from each other already
if (vn > 0.0f) return;
// collision impulse
float i = (-(1.0f + Constants.restitution) * vn) / (im1 + im2);
Vector2d impulse = mtd.normalize().multiply(i);
// change in momentum
this.velocity = this.velocity.add(impulse.multiply(im1));
ball.velocity = ball.velocity.subtract(impulse.multiply(im2));
}
Source Code: Complete source for ball to ball collider.
If anyone has some suggestions for how to improve this basic physics simulator let me know! One thing I have yet to add is angular momentum so the balls will roll more realistically. Any other suggestions? Leave a comment!
To detect whether two balls collide, just check whether the distance between their centers is less than two times the radius. To do a perfectly elastic collision between the balls, you only need to worry about the component of the velocity that is in the direction of the collision. The other component (tangent to the collision) will stay the same for both balls. You can get the collision components by creating a unit vector pointing in the direction from one ball to the other, then taking the dot product with the velocity vectors of the balls. You can then plug these components into a 1D perfectly elastic collision equation.
Wikipedia has a pretty good summary of the whole process. For balls of any mass, the new velocities can be calculated using the equations (where v1 and v2 are the velocities after the collision, and u1, u2 are from before):
If the balls have the same mass then the velocities are simply switched. Here's some code I wrote which does something similar:
void Simulation::collide(Storage::Iterator a, Storage::Iterator b)
{
// Check whether there actually was a collision
if (a == b)
return;
Vector collision = a.position() - b.position();
double distance = collision.length();
if (distance == 0.0) { // hack to avoid div by zero
collision = Vector(1.0, 0.0);
distance = 1.0;
}
if (distance > 1.0)
return;
// Get the components of the velocity vectors which are parallel to the collision.
// The perpendicular component remains the same for both fish
collision = collision / distance;
double aci = a.velocity().dot(collision);
double bci = b.velocity().dot(collision);
// Solve for the new velocities using the 1-dimensional elastic collision equations.
// Turns out it's really simple when the masses are the same.
double acf = bci;
double bcf = aci;
// Replace the collision velocity components with the new ones
a.velocity() += (acf - aci) * collision;
b.velocity() += (bcf - bci) * collision;
}
As for efficiency, Ryan Fox is right, you should consider dividing up the region into sections, then doing collision detection within each section. Keep in mind that balls can collide with other balls on the boundaries of a section, so this may make your code much more complicated. Efficiency probably won't matter until you have several hundred balls though. For bonus points, you can run each section on a different core, or split up the processing of collisions within each section.
Well, years ago I made the program like you presented here.
There is one hidden problem (or many, depends on point of view):
If the speed of the ball is too
high, you can miss the collision.
And also, almost in 100% cases your new speeds will be wrong. Well, not speeds, but positions. You have to calculate new speeds precisely in the correct place. Otherwise you just shift balls on some small "error" amount, which is available from the previous discrete step.
The solution is obvious: you have to split the timestep so, that first you shift to correct place, then collide, then shift for the rest of the time you have.
You should use space partitioning to solve this problem.
Read up on
Binary Space Partitioning
and
Quadtrees
As a clarification to the suggestion by Ryan Fox to split the screen into regions, and only checking for collisions within regions...
e.g. split the play area up into a grid of squares (which will will arbitrarily say are of 1 unit length per side), and check for collisions within each grid square.
That's absolutely the correct solution. The only problem with it (as another poster pointed out) is that collisions across boundaries are a problem.
The solution to this is to overlay a second grid at a 0.5 unit vertical and horizontal offset to the first one.
Then, any collisions that would be across boundaries in the first grid (and hence not detected) will be within grid squares in the second grid. As long as you keep track of the collisions you've already handled (as there is likely to be some overlap) you don't have to worry about handling edge cases. All collisions will be within a grid square on one of the grids.
A good way of reducing the number of collision checks is to split the screen into different sections. You then only compare each ball to the balls in the same section.
One thing I see here to optimize.
While I do agree that the balls hit when the distance is the sum of their radii one should never actually calculate this distance! Rather, calculate it's square and work with it that way. There's no reason for that expensive square root operation.
Also, once you have found a collision you have to continue to evaluate collisions until no more remain. The problem is that the first one might cause others that have to be resolved before you get an accurate picture. Consider what happens if the ball hits a ball at the edge? The second ball hits the edge and immediately rebounds into the first ball. If you bang into a pile of balls in the corner you could have quite a few collisions that have to be resolved before you can iterate the next cycle.
As for the O(n^2), all you can do is minimize the cost of rejecting ones that miss:
1) A ball that is not moving can't hit anything. If there are a reasonable number of balls lying around on the floor this could save a lot of tests. (Note that you must still check if something hit the stationary ball.)
2) Something that might be worth doing: Divide the screen into a number of zones but the lines should be fuzzy--balls at the edge of a zone are listed as being in all the relevant (could be 4) zones. I would use a 4x4 grid, store the zones as bits. If an AND of the zones of two balls zones returns zero, end of test.
3) As I mentioned, don't do the square root.
I found an excellent page with information on collision detection and response in 2D.
http://www.metanetsoftware.com/technique.html (web.archive.org)
They try to explain how it's done from an academic point of view. They start with the simple object-to-object collision detection, and move on to collision response and how to scale it up.
Edit: Updated link
You have two easy ways to do this. Jay has covered the accurate way of checking from the center of the ball.
The easier way is to use a rectangle bounding box, set the size of your box to be 80% the size of the ball, and you'll simulate collision pretty well.
Add a method to your ball class:
public Rectangle getBoundingRect()
{
int ballHeight = (int)Ball.Height * 0.80f;
int ballWidth = (int)Ball.Width * 0.80f;
int x = Ball.X - ballWidth / 2;
int y = Ball.Y - ballHeight / 2;
return new Rectangle(x,y,ballHeight,ballWidth);
}
Then, in your loop:
// Checks every ball against every other ball.
// For best results, split it into quadrants like Ryan suggested.
// I didn't do that for simplicity here.
for (int i = 0; i < balls.count; i++)
{
Rectangle r1 = balls[i].getBoundingRect();
for (int k = 0; k < balls.count; k++)
{
if (balls[i] != balls[k])
{
Rectangle r2 = balls[k].getBoundingRect();
if (r1.Intersects(r2))
{
// balls[i] collided with balls[k]
}
}
}
}
I see it hinted here and there, but you could also do a faster calculation first, like, compare the bounding boxes for overlap, and THEN do a radius-based overlap if that first test passes.
The addition/difference math is much faster for a bounding box than all the trig for the radius, and most times, the bounding box test will dismiss the possibility of a collision. But if you then re-test with trig, you're getting the accurate results that you're seeking.
Yes, it's two tests, but it will be faster overall.
This KineticModel is an implementation of the cited approach in Java.
I implemented this code in JavaScript using the HTML Canvas element, and it produced wonderful simulations at 60 frames per second. I started the simulation off with a collection of a dozen balls at random positions and velocities. I found that at higher velocities, a glancing collision between a small ball and a much larger one caused the small ball to appear to STICK to the edge of the larger ball, and moved up to around 90 degrees around the larger ball before separating. (I wonder if anyone else observed this behavior.)
Some logging of the calculations showed that the Minimum Translation Distance in these cases was not large enough to prevent the same balls from colliding in the very next time step. I did some experimenting and found that I could solve this problem by scaling up the MTD based on the relative velocities:
dot_velocity = ball_1.velocity.dot(ball_2.velocity);
mtd_factor = 1. + 0.5 * Math.abs(dot_velocity * Math.sin(collision_angle));
mtd.multplyScalar(mtd_factor);
I verified that before and after this fix, the total kinetic energy was conserved for every collision. The 0.5 value in the mtd_factor was the approximately the minumum value found to always cause the balls to separate after a collision.
Although this fix introduces a small amount of error in the exact physics of the system, the tradeoff is that now very fast balls can be simulated in a browser without decreasing the time step size.
Improving the solution to detect circle with circle collision detection given within the question:
float dx = circle1.x - circle2.x,
dy = circle1.y - circle2.y,
r = circle1.r + circle2.r;
return (dx * dx + dy * dy <= r * r);
It avoids the unnecessary "if with two returns" and the use of more variables than necessary.
After some trial and error, I used this document's method for 2D collisions : https://www.vobarian.com/collisions/2dcollisions2.pdf
(that OP linked to)
I applied this within a JavaScript program using p5js, and it works perfectly. I had previously attempted to use trigonometrical equations and while they do work for specific collisions, I could not find one that worked for every collision no matter the angle at the which it happened.
The method explained in this document uses no trigonometrical functions whatsoever, it's just plain vector operations, I recommend this to anyone trying to implement ball to ball collision, trigonometrical functions in my experience are hard to generalize. I asked a Physicist at my university to show me how to do it and he told me not to bother with trigonometrical functions and showed me a method that is analogous to the one linked in the document.
NB : My masses are all equal, but this can be generalised to different masses using the equations presented in the document.
Here's my code for calculating the resulting speed vectors after collision :
//you just need a ball object with a speed and position vector.
class TBall {
constructor(x, y, vx, vy) {
this.r = [x, y];
this.v = [0, 0];
}
}
//throw two balls into this function and it'll update their speed vectors
//if they collide, you need to call this in your main loop for every pair of
//balls.
function collision(ball1, ball2) {
n = [ (ball1.r)[0] - (ball2.r)[0], (ball1.r)[1] - (ball2.r)[1] ];
un = [n[0] / vecNorm(n), n[1] / vecNorm(n) ] ;
ut = [ -un[1], un[0] ];
v1n = dotProd(un, (ball1.v));
v1t = dotProd(ut, (ball1.v) );
v2n = dotProd(un, (ball2.v) );
v2t = dotProd(ut, (ball2.v) );
v1t_p = v1t; v2t_p = v2t;
v1n_p = v2n; v2n_p = v1n;
v1n_pvec = [v1n_p * un[0], v1n_p * un[1] ];
v1t_pvec = [v1t_p * ut[0], v1t_p * ut[1] ];
v2n_pvec = [v2n_p * un[0], v2n_p * un[1] ];
v2t_pvec = [v2t_p * ut[0], v2t_p * ut[1] ];
ball1.v = vecSum(v1n_pvec, v1t_pvec); ball2.v = vecSum(v2n_pvec, v2t_pvec);
}
I would consider using a quadtree if you have a large number of balls. For deciding the direction of bounce, just use simple conservation of energy formulas based on the collision normal. Elasticity, weight, and velocity would make it a bit more realistic.
Here is a simple example that supports mass.
private void CollideBalls(Transform ball1, Transform ball2, ref Vector3 vel1, ref Vector3 vel2, float radius1, float radius2)
{
var vec = ball1.position - ball2.position;
float dis = vec.magnitude;
if (dis < radius1 + radius2)
{
var n = vec.normalized;
ReflectVelocity(ref vel1, ref vel2, ballMass1, ballMass2, n);
var c = Vector3.Lerp(ball1.position, ball2.position, radius1 / (radius1 + radius2));
ball1.position = c + (n * radius1);
ball2.position = c - (n * radius2);
}
}
public static void ReflectVelocity(ref Vector3 vel1, ref Vector3 vel2, float mass1, float mass2, Vector3 intersectionNormal)
{
float velImpact1 = Vector3.Dot(vel1, intersectionNormal);
float velImpact2 = Vector3.Dot(vel2, intersectionNormal);
float totalMass = mass1 + mass2;
float massTransfure1 = mass1 / totalMass;
float massTransfure2 = mass2 / totalMass;
vel1 += ((velImpact2 * massTransfure2) - (velImpact1 * massTransfure2)) * intersectionNormal;
vel2 += ((velImpact1 * massTransfure1) - (velImpact2 * massTransfure1)) * intersectionNormal;
}

Resources