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

时间:2018-03-01 21:32:03

标签: javascript canvas html5-canvas trigonometry

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

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)语句执行为true,我看到log。所以我可以检测到它,但是我无法知道如何计算它之后的方向。

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

我知道如何更新我的if语句以解释这个新检测到的圈子吗?

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

3 个答案:

答案 0 :(得分:2)

所以为了做到这一点,你确实需要一些好的东西。 TRIG。您需要的基本要素是:

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

然后,由于事物以大约相等和相反的角度反弹,你需要找到该速度矢量和半径矢量之间的角度差,你可以通过使用点积。

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

我知道这有点密集,特别是如果你的三角形/矢量数学生锈了,所以这里有代码来实现它。这段代码可能会简化,但它至少展示了基本步骤:

&#13;
&#13;
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')
&#13;
<canvas id="canvas" width="500" height="500" style="border: 1px solid red; border-radius: 50%;"></canvas>
&#13;
&#13;
&#13;

免责声明:由于您的初始位置是随机的,因此在圆圈之外的球开始时效果非常好。因此,请确保初始点在范围内。

答案 1 :(得分:2)

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

获得新的速度
  

v'= v - 2 *(v•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>

答案 2 :(得分:1)

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

package app

import org.springframework.jms.core.MessageCreator

import javax.jms.JMSException
import javax.jms.Message
import javax.jms.Session

class MyJmsSenderService {

     static transactional = false
     static scope = "prototype"

     def standardSenderJmsTemplate

     def sendMessage(txt) { 
          MessageCreator messageCreator = new MessageCreator() {
           public Message createMessage(Session session) throws
           JMSException {
              return session.createTextMessage(txt)
           }
          }

        standardSenderJmsTemplate.send( "jms.checkonserver.SendingQueue", messageCreator)

        log.info "JMS message send: ${txt}"

     } 
 }

https://jsfiddle.net/d3k5pd94/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/1g9h9jvq/