簡體   English   中英

圓形/矩形碰撞響應

[英]Circle/rectangle collision response

所以我前段時間做了一個小的Breakout 克隆,我想稍微升級一下,主要是為了碰撞。 當我第一次制作它時,我在我的球和我的磚塊之間進行了基本的“碰撞”檢測,實際上將球視為另一個矩形。 但這造成了邊緣碰撞的問題,所以我想我會改變它。 問題是,我找到了一些問題的答案:

例如這張圖片

在此處輸入圖像描述

以及該線程的最后一條評論:圓形/矩形碰撞反應,但我找不到如何計算最終速度矢量。

到目前為止我有:

- 找到矩形上最近的點
- 創建法線和切線向量

現在我需要的是以某種方式“將速度矢量分為法線分量和切線分量;取反法線分量並添加法線和切線分量以獲得新的速度矢量”如果這看起來非常簡單,我很抱歉但是我無法理解那個......代碼:

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
}

謝謝 !

編輯:也發現了這一點,但我不知道它是否適用於矩形的所有點或僅適用於角落。

最好用幾個圖說明:

角

有入射角=反射角。 將此值稱為θ。

angles2

θ=法線角度 - 入射角度。

atan2是用於計算來自正x軸的矢量角度的函數。

然后緊接着是下面的代碼:

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);
}

另一種方法是沿着切線獲得速度,然后從圓周速度中減去該值的兩倍。

angles3

然后代碼變成了

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));
}

上面的兩個代碼片段幾乎在同一時間(可能)基本上完成相同的事情。 只需選擇您最了解的一個。

另外,正如@arbuthnott指出的那樣,有一個復制粘貼錯誤, NearestY應該使用rect.h而不是rect.w

編輯 :我忘記了位置分辨率。 這是將兩個物理對象分開移動以使它們不再相交的過程。 在這種情況下,由於塊是靜態的,我們只需要移動球。

滲透圖

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);
}

蝙蝠和球碰撞

處理球和矩形碰撞的最佳方法是利用系統的對稱性。

球作為一個點。

第一球,它具有一個半徑r定義的所有點r從中心的距離。 但我們可以將球轉變為一個點並將半徑添加到矩形中。 球現在只是一個隨時間移動的點,這是一條線。

矩形的四周都是半徑。 該圖顯示了這是如何工作的。

在此輸入圖像描述

綠色矩形是原始矩形。 球A,B不接觸矩形,而球C,D接觸。 球A,D代表一個特殊情況,但很容易解決,你會看到。

所有動作都是一條線。

所以現在我們有一個更大的矩形和一個球作為一個隨時間移動的點(一條線),但矩形也在移動,這意味着隨着時間的推移,邊緣會掃出對我的大腦來說太復雜的區域,所以我們再一次可以使用對稱性,這次是相對運動。

從蝙蝠的角度來看,當球在移動時它是靜止的,而在球上,它仍在蝙蝠移動時。 他們都看到對方朝着相反的方向前進。

由於球現在是一個點,改變它的運動只會改變它行進的線。 所以我們現在可以將球棒固定在太空中並從球中減去它的移動。 當蝙蝠現在固定時,我們可以將其中心點移動到原點(0,0)並將球向相反方向移動。

在這一點上,我們做了一個重要的假設。 球和球棒總是處於不接觸的狀態,當我們移動球和/或球棒時它們可能會接觸。 如果他們確實進行了接觸,我們會計算出一條新的軌跡,以便他們不會接觸。

兩次可能的碰撞

現在有兩種可能的碰撞情況,一種是球撞擊球棒側面,另一種是球撞擊球棒角落。

接下來的圖像顯示了蝙蝠在原點和球的相對於蝙蝠的運動和位置。 它沿着從A到B的紅線行進然后反彈到C

球擊中邊緣

在此輸入圖像描述

球命中角落

在此輸入圖像描述

因為這里有對稱性,所以哪個側面或角落被擊中沒有任何區別。 事實上,我們可以根據球從球棒中心的大小來反映整個問題。 因此,如果球離開球棒,則鏡像其在x方向上的位置和運動,並且在y方向上相同(您必須通過信號量跟蹤此鏡像,以便在找到解決方案后可以將其反轉)。

該示例執行上文功能doBatBall(bat, 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> 

用這種方法瑕疵。

可以用球棒捕獲球,使得沒有有效的解決方案,例如將球向下壓到屏幕的底部。 在某些時候,球的直徑大於壁和球棒之間的空間。 當發生這種情況時,解決方案將失敗並且球將通過球棒。

在演示中,我們盡一切努力不損失能量,但隨着時間的推移,浮點誤差將會累積,如果沒有輸入運行,這可能會導致能量損失。

由於球棒具有無限的動量,因此很容易將大量能量傳遞到球上,以防止球積聚到很大的動量,我已經為球增加了最大速度。 如果球的移動速度比最大速度快,則逐漸減速直到最大速度或低於最大速度。

有時如果你以相同的速度將球棒從球上移開,由於重力引起的額外加速可能導致球沒有被正確地推離球棒。

修正上面共享的想法,使用切向速度在碰撞后調整速度。

彈性 - 定義為表示碰撞后失去的力的常量

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