繁体   English   中英

如何在圆弧范围内反弹对象?

[英]How to bounce an object within circle bounds?

我有一个基本的圆圈从矩形画布的墙壁上弹起(我从一个示例中改编而成)。

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语句来解决这个新检测到的圈子吗?

看来我对基本三角学绝对可怕,所以请多多包涵! 谢谢。

因此,为了做到这一点,您确实需要一些不错的工具。 您需要的基本成分是:

  • 从圆心指向碰撞点的向量。
  • 球的速度向量

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

然后做一些trig操作以获得与半径矢量相差很大的另一个方向的新矢量(这是相等且相反的方向)。 将其设置为新的速度矢量,就可以了。

我知道这有点密集,尤其是如果您对触发/矢量数学不满意的话,那么这里是实现它的代码。 该代码可能会简化,但至少说明了基本步骤:

 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 } px += p.velX py += p.velY // boundary const dx = px - canvasRadius const dy = py - 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 = [px, py].map((c, i) => c - center[i]); // Inverse vector, this vector is one that is TANGENT to the circle at the collision point. const invvec = [-py, px]; // 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(px, py, 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> 

免责声明:由于您的初始位置是随机的,因此如果球已从圆圈外开始,则效果将不佳。 因此,请确保初始点在范围内。

您根本不需要三角函数。 您所需要的只是表面法线,它是从碰撞点到中心的向量。 对其进行归一化(将两个坐标除以长度),然后使用

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

其中v • n是点积:

v•n = vx * nx + vy * ny

翻译成您的代码示例,那就是

// 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 } px += p.velX py += p.velY // boundary const dx = px - canvasRadius const dy = py - 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(px, py, 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> 

您可以使用极坐标对向量进行归一化:

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/

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM