Я предлагаю вам перейти от кодирования специального случая к более общему подходу.
Когда сталкиваются два шара:
- Рассчитать нормальное столкновение (угол)
- Рассчитайте новую скорость на основе предыдущей скорости и нормальной
- Переместите шары так, чтобы они больше не перекрывались, предотвращая «отскок петли».
Вам понадобится:
Метод расчета угла между двумя шарами:
function ballToBallAngle(ball1,ball2) {
return Math.atan2(ball2.y-ball1.y,ball2.x-ball1.x)
}
Метод получения вектора нормали из угла:
function calcNormalFromAngle(angle){
return [
Math.cos(angle),
Math.sin(angle)
]
}
Метод расчетаскалярное произведение двух векторов:
function dotproduct (a, b){
return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n)
}
Наконец, способ расчета угла отскока. Прочитайте это , оно описывает это прекрасно.
Итак, чтобы сложить все вместе, посмотрите фрагмент ниже:
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
let balls = [
{x:40,y:40,radius:25,vx:4,vy:3},
{x:300,y:300,radius:50,vx:-2,vy:-3},
{x:100,y:220,radius:25,vx:4,vy:-3},
{x:400,y:400,radius:50,vx:-1,vy:-3},
{x:200,y:400,radius:32,vx:2,vy:-3}
]
function tick() {
balls.forEach((ball, index) => {
ball.x += ball.vx
ball.y += ball.vy
//check for x bounds collision
if (ball.x - ball.radius < 0) {
bounceBall(ball, Math.PI)
ball.x = ball.radius
} else if (ball.x + ball.radius > 500) {
bounceBall(ball, 0)
ball.x = 500 - ball.radius
}
//check for y bounds collision
if (ball.y - ball.radius < 0) {
bounceBall(ball, Math.PI / 2)
ball.y = ball.radius
} else if (ball.y + ball.radius > 500) {
bounceBall(ball, -Math.PI / 2)
ball.y = 500 - ball.radius
}
balls.forEach((other_ball, other_index) => {
if (index == other_index)
return
// how many px the balls intersect
let intersection = ball.radius + other_ball.radius - ballToBallDistance(ball, other_ball)
// if its greater than 0, they must be colliding
if (intersection > 0) {
let angle = ballToBallAngle(ball, other_ball)
let normal = calcNormalFromAngle(angle)
bounceBall(ball, angle)
bounceBall(other_ball, angle + Math.PI)
// set positions so that they are not overlapping anymore
ball.x -= normal[0] * intersection / 2
ball.y -= normal[1] * intersection / 2
other_ball.x += normal[0] * intersection / 2
other_ball.y += normal[1] * intersection / 2
}
})
})
render()
requestAnimationFrame(tick)
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
balls.forEach(ball => {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);
ctx.stroke();
})
}
function bounceBall(ball, angle) {
let normal = calcNormalFromAngle(angle)
let velocity = [ball.vx, ball.vy]
let ul = dotproduct(velocity, normal) / dotproduct(normal, normal)
let u = [
normal[0] * ul,
normal[1] * ul
]
let w = [
velocity[0] - u[0],
velocity[1] - u[1]
]
let new_velocity = [
w[0] - u[0],
w[1] - u[1]
]
ball.vx = new_velocity[0]
ball.vy = new_velocity[1]
}
function dotproduct(a, b) {
return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n)
}
function ballToBallDistance(ball1, ball2) {
return Math.sqrt((Math.pow(ball2.x - ball1.x, 2) + Math.pow(ball2.y - ball1.y, 2)));
}
function ballToBallAngle(ball1, ball2) {
return Math.atan2(ball2.y - ball1.y, ball2.x - ball1.x)
}
function calcNormalFromAngle(angle) {
return [
Math.cos(angle),
Math.sin(angle)
]
}
tick();
body{
background-color: #eee;
}
canvas{
background-color: white;
}
<canvas width="500" height="500"></canvas>