如何在圆圈范围内反弹物体?

问题描述 投票:2回答:3

我有一个基本的圆圈从矩形画布的墙壁上反弹(我改编自一个例子)。

https://jsfiddle.net/n5stvv52/1/

检查这种碰撞的代码有点粗糙,就像这样,但它有效:

if (p.x > canvasWidth - p.rad) {
  p.x = canvasWidth - p.rad
  p.velX *= -1
}
if (p.x < p.rad) {
  p.x = p.rad
  p.velX *= -1
}
if (p.y > canvasHeight - p.rad) {
  p.y = canvasHeight - p.rad
  p.velY *= -1
}
if (p.y < p.rad) {
  p.y = p.rad
  p.velY *= -1
}

其中p是移动的项目。

但是,我的画布边界现在需要是一个圆圈,所以我检查碰撞与以下内容:

const dx = p.x - canvasRadius
const dy = p.y - canvasRadius
const collision = Math.sqrt(dx * dx + dy * dy) >= canvasRadius - p.rad

if (collision) {
  console.log('Out of circle bounds!')
}

当我的球击中圆圈的边缘时,if (collision)语句执行为真,我看到log。所以我可以检测到它,但是我无法知道如何计算它之后的方向。

显然,将x与画布宽度进行比较并不是我需要的,因为这是矩形,并且在角落处切出一个圆圈。

知道如何更新我的if语句来解释这个新发现的圈子吗?

看起来基本的三角测量非常可怕,所以请耐心等待!谢谢。

javascript canvas html5-canvas trigonometry
3个回答
1
投票

您可以使用极坐标来标准化矢量:

var theta = Math.atan2(dy, dx)
var R = canvasRadius - p.rad

p.x = canvasRadius + R * Math.cos(theta)
p.y = canvasRadius + R * Math.sin(theta)

p.velX *= -1
p.velY *= -1

https://jsfiddle.net/d3k5pd94/1/

更新:如果我们为加速添加随机性,则运动可以更自然:

 p.velX *= Math.random() > 0.5 ? 1 : -1
 p.velY *= Math.random() > 0.5 ? 1 : -1

https://jsfiddle.net/1g9h9jvq/


2
投票

所以为了做到这一点,你确实需要一些好的''trig'。您需要的基本成分是:

  • 从圆心到碰撞点的矢量。
  • 球的速度矢量

然后,由于物体以大致“相等且相反的角度”反弹,您需要找到该速度矢量和半径矢量之间的角度差,您可以使用点积来获得。

然后做一些触发来获得一个新的向量,该向量与半径向量相差很远,在另一个方向上(这是你的相等和相反)。将其设置为新的速度矢量,你就可以开始了。

我知道这有点密集,特别是如果你的trig / vector数学生锈了,所以这里是代码来实现它。这段代码可能会被简化,但它至少展示了必要的步骤:

function canvasApp (selector) {
  const canvas = document.querySelector(selector)
  const context = canvas.getContext('2d')

  const canvasWidth = canvas.width
  const canvasHeight = canvas.height
  const canvasRadius = canvasWidth / 2
  const particleList = {}
  const numParticles = 1
  const initVelMax = 1.5
  const maxVelComp = 2.5
  const randAccel = 0.3
  const fadeColor = 'rgba(255,255,255,0.1)'
  let p

  context.fillStyle = '#050505'
  context.fillRect(0, 0, canvasWidth, canvasHeight)

  createParticles()
  draw()

  function createParticles () {
    const minRGB = 16
    const maxRGB = 255
    const alpha = 1

    for (let i = 0; i < numParticles; i++) {
      const vAngle = Math.random() * 2 * Math.PI
      const vMag = initVelMax * (0.6 + 0.4 * Math.random())
      const r = Math.floor(minRGB + Math.random() * (maxRGB - minRGB))
      const g = Math.floor(minRGB + Math.random() * (maxRGB - minRGB))
      const b = Math.floor(minRGB + Math.random() * (maxRGB - minRGB))
      const color = `rgba(${r},${g},${b},${alpha})`
      const newParticle = {
        x: Math.random() * canvasWidth,
        y: Math.random() * canvasHeight,
        velX: vMag * Math.cos(vAngle),
        velY: vMag * Math.sin(vAngle),
        rad: 15,
        color
      }

      if (i > 0) {
        newParticle.next = particleList.first
      }

      particleList.first = newParticle
    }
  }

  function draw () {
    context.fillStyle = fadeColor
    context.fillRect(0, 0, canvasWidth, canvasHeight)

    p = particleList.first

    // random accleration
    p.velX += (1 - 2 * Math.random()) * randAccel
    p.velY += (1 - 2 * Math.random()) * randAccel

    // don't let velocity get too large
    if (p.velX > maxVelComp) {
      p.velX = maxVelComp
    } else if (p.velX < -maxVelComp) {
      p.velX = -maxVelComp
    }
    if (p.velY > maxVelComp) {
      p.velY = maxVelComp
    } else if (p.velY < -maxVelComp) {
      p.velY = -maxVelComp
    }

    p.x += p.velX
    p.y += p.velY

    // boundary
    const dx = p.x - canvasRadius
    const dy = p.y - canvasRadius
    const collision = Math.sqrt(dx * dx + dy * dy) >= canvasRadius - p.rad

    if (collision) {
      console.log('Out of circle bounds!')
      // Center of circle.
      const center = [Math.floor(canvasWidth/2), Math.floor(canvasHeight/2)];
      // Vector that points from center to collision point (radius vector):
      const radvec = [p.x, p.y].map((c, i) => c - center[i]);
      // Inverse vector, this vector is one that is TANGENT to the circle at the collision point.
      const invvec = [-p.y, p.x];
      // Direction vector, this is the velocity vector of the ball.
      const dirvec = [p.velX, p.velY];
      
      // This is the angle in radians to the radius vector (center to collision point).
      // Time to rememeber some of your trig.
      const radangle = Math.atan2(radvec[1], radvec[0]);
      // This is the "direction angle", eg, the DIFFERENCE in angle between the radius vector
      // and the velocity vector. This is calculated using the dot product.
      const dirangle = Math.acos((radvec[0]*dirvec[0] + radvec[1]*dirvec[1]) / (Math.hypot(...radvec)*Math.hypot(...dirvec)));
      
      // This is the reflected angle, an angle that is "equal and opposite" to the velocity vec.
    	const refangle = radangle - dirangle;
      
      // Turn that back into a set of coordinates (again, remember your trig):
      const refvec = [Math.cos(refangle), Math.sin(refangle)].map(x => x*Math.hypot(...dirvec));
      
      // And invert that, so that it points back to the inside of the circle:
      p.velX = -refvec[0];
      p.velY = -refvec[1];
      
      // Easy peasy lemon squeezy!
    }

    context.fillStyle = p.color
    context.beginPath()
    context.arc(p.x, p.y, p.rad, 0, 2 * Math.PI, false)
    context.closePath()
    context.fill()

    p = p.next

    window.requestAnimationFrame(draw)
  }
}

canvasApp('#canvas')
<canvas id="canvas" width="500" height="500" style="border: 1px solid red; border-radius: 50%;"></canvas>

免责声明:由于你的初始位置是随机的,所以当球已经开始在圆圈之外时,这种效果并不好。因此,请确保初始点在范围内。


2
投票

你根本不需要三角学。你需要的只是表面法线,它是从撞击点到中心的矢量。将其标准化(将两个坐标除以长度),然后使用获得新的速度

v = c - 2 *(c•n)* n

其中v • n是点积:

v•n = v.x * n.x + v.y * n.y

转换为您的代码示例,即

// boundary
const dx = p.x - canvasRadius
const dy = p.y - canvasRadius
const nl = Math.sqrt(dx * dx + dy * dy)
const collision = nl >= canvasRadius - p.rad

if (collision) {
  // the normal at the point of collision is -dx, -dy normalized
  var nx = -dx / nl
  var ny = -dy / nl
  // calculate new velocity: v' = v - 2 * dot(d, v) * n
  const dot = p.velX * nx + p.velY * ny
  p.velX = p.velX - 2 * dot * nx
  p.velY = p.velY - 2 * dot * ny
}

function canvasApp(selector) {
  const canvas = document.querySelector(selector)
  const context = canvas.getContext('2d')

  const canvasWidth = canvas.width
  const canvasHeight = canvas.height
  const canvasRadius = canvasWidth / 2
  const particleList = {}
  const numParticles = 1
  const initVelMax = 1.5
  const maxVelComp = 2.5
  const randAccel = 0.3
  const fadeColor = 'rgba(255,255,255,0.1)'
  let p

  context.fillStyle = '#050505'
  context.fillRect(0, 0, canvasWidth, canvasHeight)

  createParticles()
  draw()

  function createParticles() {
    const minRGB = 16
    const maxRGB = 255
    const alpha = 1

    for (let i = 0; i < numParticles; i++) {
      const vAngle = Math.random() * 2 * Math.PI
      const vMag = initVelMax * (0.6 + 0.4 * Math.random())
      const r = Math.floor(minRGB + Math.random() * (maxRGB - minRGB))
      const g = Math.floor(minRGB + Math.random() * (maxRGB - minRGB))
      const b = Math.floor(minRGB + Math.random() * (maxRGB - minRGB))
      const color = `rgba(${r},${g},${b},${alpha})`
      const newParticle = {
        // start inside circle
        x: canvasWidth / 4 +  Math.random() * canvasWidth / 2,
        y: canvasHeight / 4 +  Math.random() * canvasHeight / 2,
        velX: vMag * Math.cos(vAngle),
        velY: vMag * Math.sin(vAngle),
        rad: 15,
        color
      }

      if (i > 0) {
        newParticle.next = particleList.first
      }

      particleList.first = newParticle
    }
  }

  function draw() {
    context.fillStyle = fadeColor
    context.fillRect(0, 0, canvasWidth, canvasHeight)
    
    // draw circle bounds
    context.fillStyle = "black"
    context.beginPath()
    context.arc(canvasRadius, canvasRadius, canvasRadius, 0, 2 * Math.PI, false)
    context.closePath()
    context.stroke()

    p = particleList.first

    // random accleration
    p.velX += (1 - 2 * Math.random()) * randAccel
    p.velY += (1 - 2 * Math.random()) * randAccel

    // don't let velocity get too large
    if (p.velX > maxVelComp) {
      p.velX = maxVelComp
    } else if (p.velX < -maxVelComp) {
      p.velX = -maxVelComp
    }
    if (p.velY > maxVelComp) {
      p.velY = maxVelComp
    } else if (p.velY < -maxVelComp) {
      p.velY = -maxVelComp
    }

    p.x += p.velX
    p.y += p.velY

    // boundary
    const dx = p.x - canvasRadius
    const dy = p.y - canvasRadius
    const nl = Math.sqrt(dx * dx + dy * dy)
    const collision = nl >= canvasRadius - p.rad

		if (collision) {
    	// the normal at the point of collision is -dx, -dy normalized
      var nx = -dx / nl
      var ny = -dy / nl
      // calculate new velocity: v' = v - 2 * dot(d, v) * n
      const dot = p.velX * nx + p.velY * ny
      p.velX = p.velX - 2 * dot * nx
      p.velY = p.velY - 2 * dot * ny
    }

    context.fillStyle = p.color
    context.beginPath()
    context.arc(p.x, p.y, p.rad, 0, 2 * Math.PI, false)
    context.closePath()
    context.fill()

    p = p.next

    window.requestAnimationFrame(draw)
  }
}

canvasApp('#canvas')
<canvas id="canvas" width="176" height="176"></canvas>
© www.soinside.com 2019 - 2024. All rights reserved.