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); }