简体   繁体   English

圆形/矩形碰撞响应

[英]Circle/rectangle collision response

So I built some time ago a little Breakout clone , and I wanted to upgrade it a little bit, mostly for the collisions.所以我前段时间做了一个小的Breakout 克隆,我想稍微升级一下,主要是为了碰撞。 When I first made it I had a basic " collision " detection between my ball and my brick, which in fact considered the ball as another rectangle.当我第一次制作它时,我在我的球和我的砖块之间进行了基本的“碰撞”检测,实际上将球视为另一个矩形。 But this created an issue with the edge collisions, so I thought I would change it.但这造成了边缘碰撞的问题,所以我想我会改变它。 The thing is, I found some answers to my problem:问题是,我找到了一些问题的答案:

for example this image例如这张图片

在此处输入图像描述

and the last comment of this thread: circle/rect collision reaction but i could not find how to compute the final velocity vector.以及该线程的最后一条评论:圆形/矩形碰撞反应,但我找不到如何计算最终速度矢量。

So far I have:到目前为止我有:

- Found the closest point on the rectangle , - 找到矩形上最近的点
- created the normal and tangent vectors , - 创建法线和切线向量

And now what I need is to somehow "divide the velocity vector into a normal component and a tangent component; negate the normal component and add the normal and tangent components to get the new Velocity vector" I'm sorry if this seems terribly easy but I could not get my mind around that... code:现在我需要的是以某种方式“将速度矢量分为法线分量和切线分量;取反法线分量并添加法线和切线分量以获得新的速度矢量”如果这看起来非常简单,我很抱歉但是我无法理解那个......代码:

function collision(rect, circle){
  var NearestX = Max(rect.x, Min(circle.pos.x, rect.x + rect.w));
  var NearestY = Max(rect.y, Min(circle.pos.y, rect.y + rect.w));

  var dist = createVector(circle.pos.x - NearestX, circle.pos.y - NearestY);
  var dnormal = createVector(- dist.y, dist.x);
//change current circle vel according to the collision response
}

Thanks !谢谢 !

EDIT: Also found this but I didn't know if it is applicable at all points of the rectangle or only the corners.编辑:也发现了这一点,但我不知道它是否适用于矩形的所有点或仅适用于角落。

Best explained with a couple of diagrams: 最好用几个图说明:

角

Have angle of incidence = angle of reflection. 有入射角=反射角。 Call this value θ. 将此值称为θ。

angles2

Have θ = normal angle - incoming angle. θ=法线角度 - 入射角度。

atan2 is the function for computing the angle of a vector from the positive x-axis. atan2是用于计算来自正x轴的矢量角度的函数。

Then the code below immediately follows: 然后紧接着是下面的代码:

function collision(rect, circle){
  var NearestX = Max(rect.x, Min(circle.pos.x, rect.x + rect.w));
  var NearestY = Max(rect.y, Min(circle.pos.y, rect.y + rect.h));

  var dist = createVector(circle.pos.x - NearestX, circle.pos.y - NearestY);
  var dnormal = createVector(- dist.y, dist.x);

  var normal_angle = atan2(dnormal.y, dnormal.x);
  var incoming_angle = atan2(circle.vel.y, circle.vel.x);
  var theta = normal_angle - incoming_angle;
  circle.vel = circle.vel.rotate(2*theta);
}

Another way of doing it is to get the velocity along the tangent and then subtracting twice this value from the circle velocity. 另一种方法是沿着切线获得速度,然后从圆周速度中减去该值的两倍。

angles3

Then the code becomes 然后代码变成了

function collision(rect, circle){
  var NearestX = Max(rect.x, Min(circle.pos.x, rect.x + rect.w));
  var NearestY = Max(rect.y, Min(circle.pos.y, rect.y + rect.h));

  var dist = createVector(circle.pos.x - NearestX, circle.pos.y - NearestY);
  var tangent_vel = dist.normalize().dot(circle.vel);
  circle.vel = circle.vel.sub(tangent_vel.mult(2));
}

Both of the code snippets above do basically the same thing in about the same time (probably). 上面的两个代码片段几乎在同一时间(可能)基本上完成相同的事情。 Just pick whichever one you best understand. 只需选择您最了解的一个。

Also, as @arbuthnott pointed out, there's a copy-paste error in that NearestY should use rect.h instead of rect.w . 另外,正如@arbuthnott指出的那样,有一个复制粘贴错误, NearestY应该使用rect.h而不是rect.w

Edit : I forgot the positional resolution. 编辑 :我忘记了位置分辨率。 This is the process of moving two physics objects apart so that they're no longer intersecting. 这是将两个物理对象分开移动以使它们不再相交的过程。 In this case, since the block is static, we only need to move the ball. 在这种情况下,由于块是静态的,我们只需要移动球。

渗透图

function collision(rect, circle){
  var NearestX = Max(rect.x, Min(circle.pos.x, rect.x + rect.w));
  var NearestY = Max(rect.y, Min(circle.pos.y, rect.y + rect.h));    
  var dist = createVector(circle.pos.x - NearestX, circle.pos.y - NearestY);

  if (circle.vel.dot(dist) < 0) { //if circle is moving toward the rect
    //update circle.vel using one of the above methods
  }

  var penetrationDepth = circle.r - dist.mag();
  var penetrationVector = dist.normalise().mult(penetrationDepth);
  circle.pos = circle.pos.sub(penetrationVector);
}

Bat and Ball collision 蝙蝠和球碰撞

The best way to handle ball and rectangle collision is to exploit the symmetry of the system. 处理球和矩形碰撞的最佳方法是利用系统的对称性。

Ball as a point. 球作为一个点。

First the ball, it has a radius r that defines all the points r distance from the center. 第一球,它具有一个半径r定义的所有点r从中心的距离。 But we can turn the ball into a point and add to the rectangle the radius. 但我们可以将球转变为一个点并将半径添加到矩形中。 The ball is now just a single point moving over time, which is a line. 球现在只是一个随时间移动的点,这是一条线。

The rectangle has grown on all sides by radius. 矩形的四周都是半径。 The diagram shows how this works. 该图显示了这是如何工作的。

在此输入图像描述

The green rectangle is the original rectangle. 绿色矩形是原始矩形。 The balls A,B are not touching the rectangle, while the balls C,D are touching. 球A,B不接触矩形,而球C,D接触。 The balls A,D represent a special case, but is easy to solve as you will see. 球A,D代表一个特殊情况,但很容易解决,你会看到。

All motion as a line. 所有动作都是一条线。

So now we have a larger rectangle and a ball as a point moving over time (a line), but the rectangle is also moving, which means over time the edges will sweep out areas which is too complicated for my brain, so once again we can use symmetry, this time in relative movement. 所以现在我们有一个更大的矩形和一个球作为一个随时间移动的点(一条线),但矩形也在移动,这意味着随着时间的推移,边缘会扫出对我的大脑来说太复杂的区域,所以我们再一次可以使用对称性,这次是相对运动。

From the bat's point of view it is stationary while the ball is moving, and from the ball, it is still while the bat is moving. 从蝙蝠的角度来看,当球在移动时它是静止的,而在球上,它仍在蝙蝠移动时。 They both see each other move in the opposite directions. 他们都看到对方朝着相反的方向前进。

As the ball is now a point, making changes to its movement will only change the line it travels along. 由于球现在是一个点,改变它的运动只会改变它行进的线。 So we can now fix the bat in space and subtract its movement from the ball. 所以我们现在可以将球棒固定在太空中并从球中减去它的移动。 And as the bat is now fixed we can move its center point to the origin, (0,0) and move the ball in the opposite direction. 当蝙蝠现在固定时,我们可以将其中心点移动到原点(0,0)并将球向相反方向移动。

At this point we make an important assumption. 在这一点上,我们做了一个重要的假设。 The ball and bat are always in a state that they are not touching, when we move the ball and/or bat then they may touch. 球和球棒总是处于不接触的状态,当我们移动球和/或球棒时它们可能会接触。 If they do make contact we calculate a new trajectory so that they are not touching. 如果他们确实进行了接触,我们会计算出一条新的轨迹,以便他们不会接触。

Two possible collisions 两次可能的碰撞

There are now two possible collision cases, one where the ball hits the side of the bat, and one where the ball hits the corner of the bat. 现在有两种可能的碰撞情况,一种是球撞击球棒侧面,另一种是球撞击球棒角落。

The next images show the bat at the origin and the ball relative to the bat in both motion and position. 接下来的图像显示了蝙蝠在原点和球的相对于蝙蝠的运动和位置。 It is travelling along the red line from A to B then bounces off to C 它沿着从A到B的红线行进然后反弹到C

Ball hits edge 球击中边缘

在此输入图像描述

Ball hits corner 球命中角落

在此输入图像描述

As there is symmetry here as well which side or corner is hit does not make any difference. 因为这里有对称性,所以哪个侧面或角落被击中没有任何区别。 In fact we can mirror the whole problem depending on which size the ball is from the center of the bat. 事实上,我们可以根据球从球棒中心的大小来反映整个问题。 So if the ball is left of the bat then mirror its position and motion in the x direction, and the same for the y direction (you must keep track of this mirror via a semaphore so you can reverse it once the solution is found). 因此,如果球离开球棒,则镜像其在x方向上的位置和运动,并且在y方向上相同(您必须通过信号量跟踪此镜像,以便在找到解决方案后可以将其反转)。

Code

The example does what is described above in the function doBatBall(bat, ball) The ball has some gravity and will bounce off of the sides of the canvas. 该示例执行上文功能doBatBall(bat, ball) 。球具有一定的重力并将从画布的两侧反弹。 The bat is moved via the mouse. 蝙蝠通过鼠标移动。 The bats movement will be transferred to the ball, but the bat will not feel any force from the ball. 蝙蝠的运动将转移到球上,但蝙蝠不会感受到来自球的任何力量。

 const ctx = canvas.getContext("2d"); const mouse = {x : 0, y : 0, button : false} function mouseEvents(e){ mouse.x = e.pageX; mouse.y = e.pageY; mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button; } ["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents)); // short cut vars var w = canvas.width; var h = canvas.height; var cw = w / 2; // center var ch = h / 2; const gravity = 1; // constants and helpers const PI2 = Math.PI * 2; const setStyle = (ctx,style) => { Object.keys(style).forEach(key=> ctx[key] = style[key] ) }; // the ball const ball = { r : 50, x : 50, y : 50, dx : 0.2, dy : 0.2, maxSpeed : 8, style : { lineWidth : 12, strokeStyle : "green", }, draw(ctx){ setStyle(ctx,this.style); ctx.beginPath(); ctx.arc(this.x,this.y,this.r-this.style.lineWidth * 0.45,0,PI2); ctx.stroke(); }, update(){ this.dy += gravity; var speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy); var x = this.x + this.dx; var y = this.y + this.dy; if(y > canvas.height - this.r){ y = (canvas.height - this.r) - (y - (canvas.height - this.r)); this.dy = -this.dy; } if(y < this.r){ y = this.r - (y - this.r); this.dy = -this.dy; } if(x > canvas.width - this.r){ x = (canvas.width - this.r) - (x - (canvas.width - this.r)); this.dx = -this.dx; } if(x < this.r){ x = this.r - (x - this.r); this.dx = -this.dx; } this.x = x; this.y = y; if(speed > this.maxSpeed){ // if over speed then slow the ball down gradualy var reduceSpeed = this.maxSpeed + (speed-this.maxSpeed) * 0.9; // reduce speed if over max speed this.dx = (this.dx / speed) * reduceSpeed; this.dy = (this.dy / speed) * reduceSpeed; } } } const ballShadow = { // this is used to do calcs that may be dumped r : 50, x : 50, y : 50, dx : 0.2, dy : 0.2, } // Creates the bat const bat = { x : 100, y : 250, dx : 0, dy : 0, width : 140, height : 10, style : { lineWidth : 2, strokeStyle : "black", }, draw(ctx){ setStyle(ctx,this.style); ctx.strokeRect(this.x - this.width / 2,this.y - this.height / 2, this.width, this.height); }, update(){ this.dx = mouse.x - this.x; this.dy = mouse.y - this.y; var x = this.x + this.dx; var y = this.y + this.dy; x < this.width / 2 && (x = this.width / 2); y < this.height / 2 && (y = this.height / 2); x > canvas.width - this.width / 2 && (x = canvas.width - this.width / 2); y > canvas.height - this.height / 2 && (y = canvas.height - this.height / 2); this.dx = x - this.x; this.dy = y - this.y; this.x = x; this.y = y; } } //============================================================================= // THE FUNCTION THAT DOES THE BALL BAT sim. // the ball and bat are at new position function doBatBall(bat,ball){ var mirrorX = 1; var mirrorY = 1; const s = ballShadow; // alias sx = ball.x; sy = ball.y; s.dx = ball.dx; s.dy = ball.dy; sx -= s.dx; sy -= s.dy; // get the bat half width height const batW2 = bat.width / 2; const batH2 = bat.height / 2; // and bat size plus radius of ball var batH = batH2 + ball.r; var batW = batW2 + ball.r; // set ball position relative to bats last pos sx -= bat.x; sy -= bat.y; // set ball delta relative to bat s.dx -= bat.dx; s.dy -= bat.dy; // mirror x and or y if needed if(sx < 0){ mirrorX = -1; sx = -sx; s.dx = -s.dx; } if(sy < 0){ mirrorY = -1; sy = -sy; s.dy = -s.dy; } // bat now only has a bottom, right sides and bottom right corner var distY = (batH - sy); // distance from bottom var distX = (batW - sx); // distance from right if(s.dx > 0 && s.dy > 0){ return }// ball moving away so no hit var ballSpeed = Math.sqrt(s.dx * s.dx + s.dy * s.dy); // get ball speed relative to bat // get x location of intercept for bottom of bat var bottomX = sx +(s.dx / s.dy) * distY; // get y location of intercept for right of bat var rightY = sy +(s.dy / s.dx) * distX; // get distance to bottom and right intercepts var distB = Math.hypot(bottomX - sx, batH - sy); var distR = Math.hypot(batW - sx, rightY - sy); var hit = false; if(s.dy < 0 && bottomX <= batW2 && distB <= ballSpeed && distB < distR){ // if hit is on bottom and bottom hit is closest hit = true; sy = batH - s.dy * ((ballSpeed - distB) / ballSpeed); s.dy = -s.dy; } if(! hit && s.dx < 0 && rightY <= batH2 && distR <= ballSpeed && distR <= distB){ // if hit is on right and right hit is closest hit = true; sx = batW - s.dx * ((ballSpeed - distR) / ballSpeed);; s.dx = -s.dx; } if(!hit){ // if no hit may have intercepted the corner. // find the distance that the corner is from the line segment from the balls pos to the next pos const u = ((batW2 - sx) * s.dx + (batH2 - sy) * s.dy)/(ballSpeed * ballSpeed); // get the closest point on the line to the corner var cpx = sx + s.dx * u; var cpy = sy + s.dy * u; // get ball radius squared const radSqr = ball.r * ball.r; // get the distance of that point from the corner squared const dist = (cpx - batW2) * (cpx - batW2) + (cpy - batH2) * (cpy - batH2); // is that distance greater than ball radius if(dist > radSqr){ return } // no hit // solves the triangle from center to closest point on balls trajectory var d = Math.sqrt(radSqr - dist) / ballSpeed; // intercept point is closest to line start cpx -= s.dx * d; cpy -= s.dy * d; // get the distance from the ball current pos to the intercept point d = Math.hypot(cpx - sx,cpy - sy); // is the distance greater than the ball speed then its a miss if(d > ballSpeed){ return } // no hit return sx = cpx; // position of contact sy = cpy; // find the normalised tangent at intercept point const ty = (cpx - batW2) / ball.r; const tx = -(cpy - batH2) / ball.r; // calculate the reflection vector const bsx = s.dx / ballSpeed; // normalise ball speed const bsy = s.dy / ballSpeed; const dot = (bsx * tx + bsy * ty) * 2; // get the distance the ball travels past the intercept d = ballSpeed - d; // the reflected vector is the balls new delta (this delta is normalised) s.dx = (tx * dot - bsx); s.dy = (ty * dot - bsy); // move the ball the remaining distance away from corner sx += s.dx * d; sy += s.dy * d; // set the ball delta to the balls speed s.dx *= ballSpeed; s.dy *= ballSpeed; hit = true; } // if the ball hit the bat restore absolute position if(hit){ // reverse mirror sx *= mirrorX; s.dx *= mirrorX; sy *= mirrorY; s.dy *= mirrorY; // remove bat relative position sx += bat.x; sy += bat.y; // remove bat relative delta s.dx += bat.dx; s.dy += bat.dy; // set the balls new position and delta ball.x = sx; ball.y = sy; ball.dx = s.dx; ball.dy = s.dy; } } // main update function function update(timer){ if(w !== innerWidth || h !== innerHeight){ cw = (w = canvas.width = innerWidth) / 2; ch = (h = canvas.height = innerHeight) / 2; } ctx.setTransform(1,0,0,1,0,0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0,0,w,h); // move bat and ball bat.update(); ball.update(); // check for bal bat contact and change ball position and trajectory if needed doBatBall(bat,ball); // draw ball and bat bat.draw(ctx); ball.draw(ctx); requestAnimationFrame(update); } requestAnimationFrame(update); 
 canvas { position : absolute; top : 0px; left : 0px; } body {font-family : arial; } 
 Use the mouse to move the bat and hit the ball. <canvas id="canvas"></canvas> 

Flaws with this method. 用这种方法瑕疵。

It is possible to trap the ball with the bat such that there is no valid solution, such as pressing the ball down onto the bottom of the screen. 可以用球棒捕获球,使得没有有效的解决方案,例如将球向下压到屏幕的底部。 At some point the balls diameter is greater than the space between the wall and the bat. 在某些时候,球的直径大于壁和球棒之间的空间。 When this happens the solution will fail and the ball will pass through the bat. 当发生这种情况时,解决方案将失败并且球将通过球棒。

In the demo there is every effort made to not loss energy, but over time floating point errors will accumulate, this can lead to a loss of energy if the sim is run without some input. 在演示中,我们尽一切努力不损失能量,但随着时间的推移,浮点误差将会累积,如果没有输入运行,这可能会导致能量损失。

As the bat has infinite momentum it is easy to transfer a lot of energy to the ball, to prevent the ball accumulating to much momentum I have added a max speed to the ball. 由于球棒具有无限的动量,因此很容易将大量能量传递到球上,以防止球积聚到很大的动量,我已经为球增加了最大速度。 if the ball moves quicker than the max speed it is gradually slowed down until at or under the max speed. 如果球的移动速度比最大速度快,则逐渐减速直到最大速度或低于最大速度。

On occasion if you move the bat away from the ball at the same speed, the extra acceleration due to gravity can result in the ball not being pushed away from the bat correctly. 有时如果你以相同的速度将球棒从球上移开,由于重力引起的额外加速可能导致球没有被正确地推离球棒。

Correction of an idea shared above, with adjusting velocity after collision using tangental velocity.修正上面共享的想法,使用切向速度在碰撞后调整速度。

bounciness - constant defined to represent lost force after collision弹性 - 定义为表示碰撞后失去的力的常量

nv = vector                            # normalized vector from center of cricle to collision point        (normal)
pv = [-vector[1], vector[0]]           # normalized vector perpendicular to nv                             (tangental)
n = dot_product(nv, circle.vel)        # normal vector length
t = dot_product(pv, circle.vel)        # tangental_vector length
new_v = sum_vectors(multiply_vector(t*bounciness, pv), multiply_vector(-n*self.bounciness, nv))            # new velocity vector
circle.velocity = new_v

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

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