- The same as in level 4, but using spring collisions and having more accuracy when multiple balls collide simulatenously (apparent after the kick-off at the beginning).
- Simulate the motion of multiple balls.
- There is no gravity, our simulation may be interpreted as a top-down view on a billiard table.
- Collisions can happen with all the walls, and between each of the balls.
- All balls, except one, are initially stationary.
-
The spring collisions for ball-ball collisions work exactly like the spring collisions when a ball collides with a wall (level 5).
-
The deformation process is modeled by a spring that sits between the two balls with initial length 0 and which is extended when the balls bump deeper into each other.
- The extended spring then deaccelerates both balls until they have the same velocity and then pulls them out of each other again, by applying an opposite but equal force on both that is proportional to its extension length, according to Hooke's Law.
$$ \begin{aligned} F'{j,x,i} &= \begin{cases} s \cdot k \cdot \dfrac{d_x}{d}, & \text{if collision}\ 0, & \text{otherwise} \end{cases}\[16pt] F'{j,y,i} &= \begin{cases} s \cdot k \cdot \dfrac{d_y}{d}, & \text{if collision}\ 0, & \text{otherwise} \end{cases}\[18pt] \end{aligned} $$
$$ \begin{aligned} F'{i,x,j} &= - F'{j,x,i}\ F'{i,y,j} &= - F'{j,y,i}\[10pt] \end{aligned} $$
$$ \begin{aligned} F'{i,x} &= \sum{\text{other balls } j}F'{i,x,j}\[12pt] F'{i,y} &= \sum_{\text{other balls } j}F'_{i,y,j}\[28pt] \end{aligned} $$
$$ \begin{aligned} F''{i,x} = \begin{cases} F'{i,x} + (r_i-x_i) \cdot k, & \text{if}\quad 0 < r_i-x_i\ F'{i,x} - (x_i+r_i-w) \cdot k,\quad & \text{if}\quad 0 < x_i+r_i-w\ F'{i,x}, & \text{otherwise} \end{cases}\[24pt] \end{aligned} $$
$$ \begin{aligned} F''{i,y} = \begin{cases} F'{i,y} + (r_i-y_i) \cdot k, &\text{if}\quad 0 < r_i-y_i\ F'{i,y} - (y_i+r_i-h) \cdot k,\quad & \text{if}\quad 0 < y_i+r_i-h\ F'{i,y}, & \text{otherwise} \end{cases}\[24pt] \end{aligned} $$
$$ \begin{aligned} a'{i,x} &= \frac{F''{i,x}}{m}\[8pt] a'{i,y} &= \frac{F''{i,y}}{m}\[8pt] \end{aligned} $$
$$ \begin{aligned} v'{i,x} &= v{i,x} + dv_{i,x} & &\leftarrow & dv_{i,x} &= dt \cdot a'{i,x}\[4pt] v'{i,y} &= v_{i,y} + dv_{i,y} & &\leftarrow & dv_{i,y} &= dt \cdot a'_{i,y}\[8pt] \end{aligned} $$
$$ \begin{aligned} x'i &= x_i + dx_i & &\leftarrow & dx_i &= dt \cdot v'{i,x}\[4pt] y'i &= y_i + dy_i & &\leftarrow & dy_i &= dt \cdot v'{i,y}\[14pt] \end{aligned} $$
const balls = [
{
x: 30, // at the left
y: 0.5 * canvas.h, // vertically centered
v_x: 10, // moving straight to the right
v_y: 0, //
m: 1,
r: 15,
color: '#E91E63',
},
{
x: canvas.w - 50, // at the right
y: 0.5 * canvas.h, // vertically centered
v_x: 0, // initially stationary
v_y: 0, //
m: 1,
r: 15,
color: '#00BCD4',
},
// ...
];
const k = 5; // spring stiffness
function simulateOneStep(dt) {
for (let ball of balls) {
ball.F_x = ball.F_y = 0; // reset/initialize
}
forEachPair(balls, (i, j) => {
const d_x = i.x - j.x;
const d_y = i.y - j.y;
const d = Math.sqrt(d_x ** 2 + d_y ** 2) + 0.000001; // '+0.000001' prevents division with zero later
const s = i.r + j.r - d;
if (0 < s) {
i.F_x += s * k * (d_x / d);
i.F_y += s * k * (d_y / d);
j.F_x -= s * k * (d_x / d);
j.F_y -= s * k * (d_y / d);
}
});
for (let ball of balls) {
if (ball.x - ball.r < 0) {
ball.F_x -= (ball.x - ball.r) * k;
}
if (ball.x + ball.r - canvas.w > 0) {
ball.F_x -= (ball.x + ball.r - canvas.w) * k;
}
if (ball.y - ball.r < 0) {
ball.F_y -= (ball.y - ball.r) * k;
}
if (ball.y + ball.r - canvas.h > 0) {
ball.F_y -= (ball.y + ball.r - canvas.h) * k;
}
}
for (let ball of balls) {
const a_x = ball.F_x / ball.m;
const a_y = ball.F_y / ball.m;
ball.v_x += dt * a_x;
ball.v_y += dt * a_y;
ball.x += dt * ball.v_x;
ball.y += dt * ball.v_y;
}
}
function forEachPair(array, callback) {
for (let i = 0; i < array.length; i++) { // loop every item
for (let j = i + 1; j < array.length; j++) { // loop every item after the current item
callback(array[i], array[j], i, j);
}
}
}
- See level 5.
Code | Code Live |