简体   繁体   English

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

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

I have a basic circle bouncing off the walls of a rectangle canvas (that I adapted from an example). 我有一个基本的圆圈从矩形画布的墙壁上弹起(我从一个示例中改编而成)。

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

The code to check for this kind of collision is somewhat crude, like so, but it works: 像这样,用于检查这种冲突的代码有些粗糙,但是可以起作用:

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
}

Where p is the item moving around. 其中p是四处移动的项目。

However, the bounds of my canvas now need to be a circle, so I check collision with the following: 但是,我的画布的边界现在必须是一个圆,因此我检查以下内容是否冲突:

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!')
}

When my ball hits the edges of the circle, the if (collision) statement executes as true and I see the log . 当我的球碰到圆的边缘时, if (collision)语句执行为true,并且我看到了log So I can get it detected, but I'm unable to know how to calculate the direction it should then go after that. 因此,我可以检测到它,但是我不知道如何计算之后应该走的方向。

Obviously comparing x to the canvas width isn't what I need because that's the rectangle and a circle is cut at the corners. 显然,将x与画布宽度进行比较并不是我所需要的,因为那是矩形,并且在角处切了一个圆。

Any idea how I can update my if statements to account for this newly detected circle? 知道如何更新if语句来解决这个新检测到的圈子吗?

I'm absolutely terrible with basic trigonometry it seems, so please bear with me! 看来我对基本三角学绝对可怕,所以请多多包涵! Thank you. 谢谢。

So in order to do this you will indeed need some good ol' trig. 因此,为了做到这一点,您确实需要一些不错的工具。 The basic ingredients you'll need are: 您需要的基本成分是:

  • The vector that points from the center of the circle to the collision point. 从圆心指向碰撞点的向量。
  • The velocity vector of the ball 球的速度向量

Then, since things bounce with roughly an "equal and opposite angle", you'll need to find the angle difference between that velocity vector and the radius vector, which you can get by using a dot product. 然后,由于事物以大约“相等且相反的角度”反弹,因此您需要找到该速度矢量和半径矢量之间的角度差,您可以使用点积获得该角度差。

Then do some trig to get a new vector that is that much off from the radius vector, in the other direction (this is your equal and opposite). 然后做一些trig操作以获得与半径矢量相差很大的另一个方向的新矢量(这是相等且相反的方向)。 Set that to be the new velocity vector, and you're good to go. 将其设置为新的速度矢量,就可以了。

I know that's a bit dense, especially if you're rusty with your trig / vector math, so here's the code to get it going. 我知道这有点密集,尤其是如果您对触发/矢量数学不满意的话,那么这里是实现它的代码。 This code could probably be simplified but it demonstrates the essential steps at least: 该代码可能会简化,但至少说明了基本步骤:

 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> 

DISCLAIMER: Since your initial position is random, this doens't work very well with the ball starts already outside of the circle. 免责声明:由于您的初始位置是随机的,因此如果球已从圆圈外开始,则效果将不佳。 So make sure the initial point is within the bounds. 因此,请确保初始点在范围内。

You don't need trigonometry at all. 您根本不需要三角函数。 All you need is the surface normal, which is the vector from the point of impact to the center. 您所需要的只是表面法线,它是从碰撞点到中心的向量。 Normalize it (divide both coordinates by the length), and you get the new velocity using 对其进行归一化(将两个坐标除以长度),然后使用

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

Where v • n is the dot product: 其中v • n是点积:

v • n = vx * nx + vy * ny v•n = vx * nx + vy * ny

Translated to your code example, that's 翻译成您的代码示例,那就是

// 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> 

You can use the polar coordinates to normalize the vector: 您可以使用极坐标对向量进行归一化:

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

Update : The movement can be more natural if we add randomness to acceleration: 更新 :如果我们给加速度添加随机性,则运动会更加自然:

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

https://jsfiddle.net/1g9h9jvq/ https://jsfiddle.net/1g9h9jvq/

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

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