在 JS 画布动画中为台球物理添加重力

问题描述

我正在尝试使用 Javascript 编写一个小型物理演示。我有多个球可以很好地相互反弹,但是当我尝试添加重力时出现问题。

我试图在它们撞击后保持动量守恒,但是当我为每个重力增加恒定重力时,物理开始崩溃。

以下是我所拥有的代码

class Ball {
  constructor ({
    x,y,vx,vy,radius,color = 'red',}) {
    this.x = x
    this.y = y
    this.vx = vx
    this.vy = vy
    this.radius = radius
    this.color = color

    this.mass = 1
  }
  render (ctx) {
    ctx.save()
    ctx.fillStyle = this.color
    ctx.strokeStyle = this.color
    ctx.translate(this.x,this.y)
    ctx.strokeRect(-this.radius,-this.radius,this.radius * 2,this.radius * 2)
    ctx.beginPath()
    ctx.arc(0,this.radius,Math.PI * 2,false)
    ctx.closePath()
    ctx.fill()
    ctx.restore()
    return this
  }
  getBounds () {
    return {
      x: this.x - this.radius,y: this.y - this.radius,width: this.radius * 2,height: this.radius * 2
    }
  }
}

const intersects = (rectA,rectB) => {
  return !(rectA.x + rectA.width < rectB.x ||
           rectB.x + rectB.width < rectA.x ||
           rectA.y + rectA.height < rectB.y ||
           rectB.y + rectB.height < rectA.y)
}

const checkWall = (ball) => {
  const bounceFactor = 0.5
  if (ball.x + ball.radius > canvas.width) {
    ball.x = canvas.width - ball.radius
    ball.vx *= -bounceFactor
  }
  if (ball.x - ball.radius < 0) {
    ball.x = ball.radius
    ball.vx *= -bounceFactor
  }
  if (ball.y + ball.radius > canvas.height) {
    ball.y = canvas.height - ball.radius
    ball.vy *= -1
  }
  if (ball.y - ball.radius < 0) {
    ball.y = ball.radius
    ball.vy *= -bounceFactor
  }
}

const rotate = (x,sin,cos,reverse) => {
  return {
     x: reverse ? x * cos + y * sin : x * cos - y * sin,y: reverse ? y * cos - x * sin : y * cos + x * sin
   }
}

const checkCollision = (ball0,ball1,dt) => {
  const dx = ball1.x - ball0.x
  const dy = ball1.y - ball0.y
  const dist = Math.sqrt(dx * dx + dy * dy)
  const mindist = ball0.radius + ball1.radius
  if (dist < mindist) {
    //calculate angle,sine,and cosine
    const angle = Math.atan2(dy,dx)
    const sin = Math.sin(angle)
    const cos = Math.cos(angle)
    
    //rotate ball0's position
    const pos0 = {x: 0,y: 0}
    
    //rotate ball1's position
    const pos1 = rotate(dx,dy,true)
    
    //rotate ball0's veLocity
    const vel0 = rotate(ball0.vx,ball0.vy,true)
    
    //rotate ball1's veLocity
    const vel1 = rotate(ball1.vx,ball1.vy,true)
    
    //collision reaction
    const vxTotal = (vel0.x - vel1.x)
    vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) /
      (ball0.mass + ball1.mass)
    vel1.x = vxTotal + vel0.x
    
    const absV = Math.abs(vel0.x) + Math.abs(vel1.x)
    const overlap = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x)
    pos0.x += vel0.x / absV * overlap
    pos1.x += vel1.x / absV * overlap
    
    //rotate positions back
    const pos0F = rotate(pos0.x,pos0.y,false)
    const pos1F = rotate(pos1.x,pos1.y,false)
    
    //adjust positions to actual screen positions
    ball1.x = ball0.x + pos1F.x
    ball1.y = ball0.y + pos1F.y
    ball0.x = ball0.x + pos0F.x
    ball0.y = ball0.y + pos0F.y
    
    //rotate veLocities back
    const vel0F = rotate(vel0.x,vel0.y,false)
    const vel1F = rotate(vel1.x,vel1.y,false)
    
    ball0.vx = vel0F.x
    ball0.vy = vel0F.y
    ball1.vx = vel1F.x
    ball1.vy = vel1F.y
  }
}


const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')

let oldTime = 0

canvas.width = innerWidth
canvas.height = innerHeight
document.body.appendChild(canvas)

const log = document.getElementById('log')

const balls = new Array(36).fill(null).map(_ => new Ball({
  x: Math.random() * innerWidth,y: Math.random() * innerHeight,vx: (Math.random() * 2 - 1) * 5,vy: (Math.random() * 2 - 1) * 5,radius: 20,}))

requestAnimationFrame(updateFrame)

function updateFrame (ts) {
  const dt = ts - oldTime
  oldTime = ts

  ctx.clearRect(0,innerWidth,innerHeight)
  
  for (let i = 0; i < balls.length; i++) {
    const ball = balls[i]
    // ADD GraviTY HERE
    ball.vy += 2
    ball.x += ball.vx * (dt * 0.005)
    ball.y += ball.vy * (dt * 0.005)
    checkWall(ball)
  }
  
  for (let i = 0; i < balls.length; i++) {
    const ball0 = balls[i]
    for (let j = i + 1; j < balls.length; j++) {
      const ball1 = balls[j]
      // CHECK FOR COLLISIONS HERE
      checkCollision(ball0,dt)
    }
  }
  
  for (let i = 0; i < balls.length; i++) {
    const ball = balls[i]
    ball.render(ctx)
  }
  
  // const dist = ball2.x - ball1.x
//   if (Math.abs(dist) < ball1.radius + ball2.radius) {
//     const vxTotal = ball1.vx - ball2.vx
//     ball1.vx = ((ball1.mass - ball2.mass) * ball1.vx + 2 * ball2.mass * ball2.vx) / (ball1.mass + ball2.mass)
//     ball2.vx = vxTotal + ball1.vx

//     ball1.x += ball1.vx
//     ball2.x += ball2.vx
//   }
  

//     ball.vy += 0.5
//     ball.x += ball.vx
//     ball.y += ball.vy

//     

//     ball.render(ctx)
  
  requestAnimationFrame(updateFrame)
}
* { margin: 0; padding: 0; }

如您所见,我有 checkCollision 辅助方法,该方法计算一个球与另一个球碰撞后的动能和新速度。我的更新循环如下所示:

  // add veLocities to balls position
  // check if its hitting any wall and bounce it back
  for (let i = 0; i < balls.length; i++) {
    const ball = balls[i]
    // Add constant gravity to the vertical veLocity
    // When balls stack up on each other at the bottom,the gravity is still applied and my
    // "checkCollision" method freaks out and the physics start to explode
    ball.vy += 0.8
    ball.x += ball.vx * (dt * 0.005)
    ball.y += ball.vy * (dt * 0.005)
    checkWall(ball)
  }
  
  for (let i = 0; i < balls.length; i++) {
    const ball0 = balls[i]
    for (let j = i + 1; j < balls.length; j++) {
      const ball1 = balls[j]
      // Check collisions between two balls
      checkCollision(ball0,dt)
    }
  }
  
  // Finally render the ball on-screen
  for (let i = 0; i < balls.length; i++) {
    const ball = balls[i]
    ball.render(ctx)
  }

如何计算重力,同时防止球开始堆叠时物理爆炸?

似乎重力正在与“checkCollision”方法发生碰撞。 checkCollision 方法试图将它们移回原位,但恒定的重力覆盖了它并继续将它们拉下。

编辑:经过一些阅读,我明白一些 Verlet 集成是有序的,但我很难理解它。

for (let i = 0; i < balls.length; i++) {
        const ball = balls[i]
        // This line needs to be converted to verlet motion?
        ball.vy += 2
        ball.x += ball.vx * (dt * 0.005)
        ball.y += ball.vy * (dt * 0.005)
        checkWall(ball)
      }

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)