p5.j​​s中的角碰撞角度

时间:2019-03-29 14:04:03

标签: javascript processing collision-detection physics p5.js

摘要:

为了在移动的矩形和下降的圆之间建立一个简单的碰撞检测系统,我想使其更加逼真。

主要问题:

-我要解决的主要问题是检测圆对象何时击中矩形的角,并相应地使圆基于该角度弹回。

代码:

var balls = [];
var obstacle;

function setup() {
  createCanvas(400, 400);
  obstacle = new Obstacle();
}

function draw() {
  background(75);
  obstacle.display();
  obstacle.update();
  
  for (var i = 0; i < balls.length; i++) {
    balls[i].display();
	  if (!RectCircleColliding(balls[i], obstacle)){
        balls[i].update();
        balls[i].edges();
	  }
    
    //console.log(RectCircleColliding(balls[i], obstacle));
  }
}

function mousePressed() {
  balls.push(new Ball(mouseX, mouseY));
}

function Ball(x, y) {
  this.x = x;
  this.y = y;
  this.r = 15;
  this.gravity = 0.5;
  this.velocity = 0;
  this.display = function() {
    fill(255, 0, 100);
    stroke(255);
    ellipse(this.x, this.y, this.r * 2);
  }
  this.update = function() {
    this.velocity += this.gravity;
    this.y += this.velocity;
  }
  this.edges = function() {
    if (this.y >= height - this.r) {
      this.y = height - this.r;
      this.velocity = this.velocity * -1;
      this.gravity = this.gravity * 1.1;
    }
  }
}

function Obstacle() {
  this.x = width - width;
  this.y = height / 2;
  this.w = 200;
  this.h = 25;

  this.display = function() {
    fill(0);
    stroke(255);
    rect(this.x, this.y, this.w, this.h);
  }
  
  this.update = function() {
    this.x++;
    
    if (this.x > width + this.w /2) {
       this.x = -this.w;
    }
  }
}

function RectCircleColliding(Ball, Obstacle) {
  // define obstacle borders
  var oRight = Obstacle.x + Obstacle.w;
  var oLeft = Obstacle.x;
  var oTop = Obstacle.y;
  var oBottom = Obstacle.y + Obstacle.h;

  //compare ball's position (acounting for radius) with the obstacle's border
  if (Ball.x + Ball.r > oLeft) {
    if (Ball.x - Ball.r < oRight) {
      if (Ball.y + Ball.r > oTop) {
        if (Ball.y - Ball.r < oBottom) {
          
         let oldY = Ball.y;
         Ball.y = oTop - Ball.r;
         Ball.velocity = Ball.velocity * -1;
           if (Ball.gravity < 2.0){
              Ball.gravity = Ball.gravity * 1.1;  
           } else {
             Ball.velocity = 0;
             Ball.y = oldY;
         }   
         return (true);
        } 
      }
    }
  }
    return false;
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>

预期输出:

我希望看到下降的圆相对于矩形所撞击的位置从矩形上反弹。

如果圆圈碰到角落,它们的弹跳和击中死点应该会有所不同。

2 个答案:

答案 0 :(得分:3)

先决条件

球的速度必须是矢量(XY分量),而不仅仅是一个数字。


1。确定圆是否会碰到侧面或角落

获取从矩形中心到圆的矢量分量,并根据矩形的尺寸对其进行检查:

// Useful temporary variables for later use
var hx = 0.5 * obstacle.w;
var hy = 0.5 * obstacle.h;
var rx = obstacle.x + hx;
var ry = obstacle.y + hy;

// displacement vector
var dx = ball.x - rx;
var dy = ball.y - ry;

// signs
var sx = dx < -hx ? -1 : (dx > hx ? 1 : 0);
var sy = dy < -hy ? -1 : (dy > hy ? 1 : 0);

如果两个sx, sy都不为零,则球可能会撞到一个角,否则可能撞到一侧。


2。确定圆是否碰撞

将每个符号乘以相应的半尺寸:

// displacement vector from the nearest point on the rectangle
var tx = sx * (Math.abs(dx) - hx);
var ty = sy * (Math.abs(dy) - hy);

// distance from p to the center of the circle
var dc = Math.hypot(tx, ty);

if (dc <= ball.r) {
    /* they do collide */
}

3。确定碰撞法线向量

(tx, ty)是法线向量的组成部分,但前提是球的中心在矩形的外部:

// epsilon to account for numerical imprecision
const EPSILON = 1e-6;

var nx = 0, ny = 0, nl = 0;
if (sx == 0 && sy == 0) {  // center is inside
  nx = dx > 0 ? 1 : -1;
  ny = dy > 0 ? 1 : -1;
  nl = Math.hypot(nx, ny);
} else {                   // outside
  nx = tx;
  ny = ty;
  nl = dc;
}
nx /= nl;
ny /= nl;

4。解决任何“渗透”

(请不要玩笑)

这确保了球永远不会刺入矩形的表面,从而提高了碰撞的视觉质量:

ball.x += (ball.r - dc) * nx; 
ball.y += (ball.r - dc) * ny;

5。解决冲突

如果圆沿法线方向行进,则不要解决碰撞,因为球可能会粘在表面上

// dot-product of velocity with normal
var dv = ball.vx * nx + ball.vy * ny;

if (dv >= 0.0) {
    /* exit and don't do anything else */
}

// reflect the ball's velocity in direction of the normal
ball.vx -= 2.0 * dv * nx;
ball.vy -= 2.0 * dv * ny;

有效的JS代码段

const GRAVITY = 250.0;

function Ball(x, y, r) {
  this.x = x;
  this.y = y;
  this.r = r;
  this.vx = 0;
  this.vy = 0;

  this.display = function() {
    fill(255, 0, 100);
    stroke(255);
    ellipse(this.x, this.y, this.r * 2);
  }

  this.collidePage = function(b) {
    if (this.vy > 0 && this.y + this.r >= b) {
      this.y = b - this.r;
      this.vy = -this.vy;
    }
  }

  this.updatePosition = function(dt) {
    this.x += this.vx * dt;
    this.y += this.vy * dt;
  }
  this.updateVelocity = function(dt) {
    this.vy += GRAVITY * dt;
  }
}

function Obstacle(x, y, w, h) {
  this.x = x;
  this.y = y;
  this.w = w;
  this.h = h;

  this.display = function() {
    fill(0);
    stroke(255);
    rect(this.x, this.y, this.w, this.h);
  }

  this.update = function() {
    this.x++;
    if (this.x > width + this.w /2) {
      this.x = -this.w;
    }
  }
}

var balls = [];
var obstacle;

function setup() {
  createCanvas(400, 400);
  obstacle = new Obstacle(0, height / 2, 200, 25);
}

const DT = 0.05;

function draw() {
  background(75);
  obstacle.update();
  obstacle.display();
  for (var i = 0; i < balls.length; i++) {
    balls[i].updatePosition(DT);
    balls[i].collidePage(height);
    ResolveRectCircleCollision(balls[i], obstacle);
    balls[i].updateVelocity(DT);
    balls[i].display();
  }
}

function mousePressed() {
  balls.push(new Ball(mouseX, mouseY, 15));
}

const EPSILON = 1e-6;

function ResolveRectCircleCollision(ball, obstacle) {
  var hx = 0.5 * obstacle.w, hy = 0.5 * obstacle.h;
  var rx = obstacle.x + hx, ry = obstacle.y + hy;
  var dx = ball.x - rx, dy = ball.y - ry;

  var sx = dx < -hx ? -1 : (dx > hx ? 1 : 0);
  var sy = dy < -hy ? -1 : (dy > hy ? 1 : 0);

  var tx = sx * (Math.abs(dx) - hx);
  var ty = sy * (Math.abs(dy) - hy);
  var dc = Math.hypot(tx, ty);
  if (dc > ball.r)
    return false;

  var nx = 0, ny = 0, nl = 0;
  if (sx == 0 && sy == 0) {
    nx = dx > 0 ? 1 : -1; ny = dy > 0 ? 1 : -1;
    nl = Math.hypot(nx, ny);
  } else {
    nx = tx; ny = ty;
    nl = dc;
  }
  nx /= nl; ny /= nl;

  ball.x += (ball.r - dc) * nx; ball.y += (ball.r - dc) * ny;

  var dv = ball.vx * nx + ball.vy * ny;
  if (dv >= 0.0)
    return false;
  ball.vx -= 2.0 * dv * nx; ball.vy -= 2.0 * dv * ny;

  return true;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

答案 1 :(得分:1)

我将尝试提出一个解决方案,该解决方案尽可能与原始代码保持一致。解决方案旨在解决该问题中提供的代码。

selv.sideV对象添加侧向移动(Ball),该对象由0初始化:

function Ball(x, y) {
    this.x = x;
    this.y = y;
    this.r = 15;
    this.gravity = 0.5;
    this.velocity = 0;
    this.sideV = 0 

    // ...
}

通过侧向移动将球移动到update中的一侧,并减小侧向移动:

this.update = function() {
    this.velocity += this.gravity;
    this.y += this.velocity;
    this.x += this.sideV;
    this.sideV *= 0.98;
}

为2个盒子的相交测试创建函数:

function IsectRectRect(l1, r1, t1, b1, l2, r2, t2, b2) {
    return l1 < r2 && l2 < r1 && t1 < b2 && t2 < b1;
}

还有一个函数,可以计算入射向量R对表面V的法线向量的反射向量N(如台球的反射):

function reflect( V, N ) {
    R = V.copy().sub(N.copy().mult(2.0 * V.dot(N)));
    return R;
}

BallObstacle发生冲突时,您必须处理3种情况。

  1. 球完全击中Obstacle的顶部:IsectRectRect(oL, oR, oT, oB, Ball.x, Ball.x, bT, bB)
  2. 球击中ObstacleIsectRectRect(oL, oL, oT, oB, bL, bR, bT, bB)的左边缘
  3. 球击中Obstacle的右边缘:IsectRectRect(oR, oR, oT, oB, bL, bR, bT, bB)

在每种情况下,必须计算反射的法向矢量。这是从Obstacle的顶部或边缘到Ball的中心的向量。
使用功能reflectBall上反弹Obstacle

function RectCircleColliding(Ball, Obstacle) {
    let oL = Obstacle.x;
    let oR = Obstacle.x + Obstacle.w;
    let oT = Obstacle.y;
    let oB = Obstacle.y + Obstacle.h;
    let bL = Ball.x - Ball.r;
    let bR = Ball.x + Ball.r;
    let bT = Ball.y - Ball.r;
    let bB = Ball.y + Ball.r;

    let isect = false;
    let hitDir = createVector(0, 1);
    if ( IsectRectRect(oL, oR, oT, oB, Ball.x, Ball.x, bT, bB) ) {
        isect = true;
    } else if ( IsectRectRect(oL, oL, oT, oB, bL, bR, bT, bB) ) {
        hitDir = createVector(Ball.x, Ball.y).sub(createVector(oL, oT))
        isect = hitDir.mag() < Ball.r;
    } else if ( IsectRectRect(oR, oR, oT, oB, bL, bR, bT, bB) ) {
        hitDir = createVector(Ball.x, Ball.y).sub(createVector(oR, oT))
        isect = hitDir.mag() < Ball.r;
    }

    if ( isect ) {
        let dir = createVector(Ball.sideV, Ball.velocity);
        R = reflect(dir, hitDir.normalize());
        Ball.velocity = R.y;
        Ball.sideV = R.x;
        let oldY = Ball.y;
        Ball.y = oT - Ball.r;
        if (Ball.gravity < 2.0){
            Ball.gravity = Ball.gravity * 1.1;  
        } else {
            Ball.velocity = 0;
            Ball.y = oldY;
        }   
        return true;
    }
    return false;
}

请参见示例,其中我将更改应用于您的原始代码:

var balls = [];
var obstacle;

function setup() {
  createCanvas(400, 400);
  obstacle = new Obstacle();
}

function draw() {
  background(75);
  obstacle.display();
  obstacle.update();

  for (var i = 0; i < balls.length; i++) {
    balls[i].display();
      if (!RectCircleColliding(balls[i], obstacle)){
        balls[i].update();
        balls[i].edges();
      }

    //console.log(RectCircleColliding(balls[i], obstacle));
  }
}

function mousePressed() {
  balls.push(new Ball(mouseX, mouseY));
}

function Ball(x, y) {
  this.x = x;
  this.y = y;
  this.r = 15;
  this.gravity = 0.5;
  this.velocity = 0;
  this.sideV = 0 
  this.display = function() {
    fill(255, 0, 100);
    stroke(255);
    ellipse(this.x, this.y, this.r * 2);
  }
  this.update = function() {
      this.velocity += this.gravity;
      this.y += this.velocity;
      this.x += this.sideV;
      this.sideV *= 0.98;
  }
  this.edges = function() {
    if (this.y >= height - this.r) {
      this.y = height - this.r;
      this.velocity = this.velocity * -1;
      this.gravity = this.gravity * 1.1;
    }
  }
}

function Obstacle() {
  this.x = width - width;
  this.y = height / 2;
  this.w = 200;
  this.h = 25;

  this.display = function() {
    fill(0);
    stroke(255);
    rect(this.x, this.y, this.w, this.h);
  }

  this.update = function() {
    this.x++;

    if (this.x > width + this.w /2) {
       this.x = -this.w;
    }
  }
}

function IsectRectRect(l1, r1, t1, b1, l2, r2, t2, b2) {
    return l1 < r2 && l2 < r1 && t1 < b2 && t2 < b1;
}

function reflect( V, N ) {
    R = V.copy().sub(N.copy().mult(2.0 * V.dot(N)));
    return R;
}

function RectCircleColliding(Ball, Obstacle) {
    let oL = Obstacle.x;
    let oR = Obstacle.x + Obstacle.w;
    let oT = Obstacle.y;
    let oB = Obstacle.y + Obstacle.h;
    let bL = Ball.x - Ball.r;
    let bR = Ball.x + Ball.r;
    let bT = Ball.y - Ball.r;
    let bB = Ball.y + Ball.r;

    let isect = false;
    let hitDir = createVector(0, 1);
    if ( IsectRectRect(oL, oR, oT, oB, Ball.x, Ball.x, bT, bB) ) {
        isect = true;
    } else if ( IsectRectRect(oL, oL, oT, oB, bL, bR, bT, bB) ) {
        hitDir = createVector(Ball.x, Ball.y).sub(createVector(oL, oT))
        isect = hitDir.mag() < Ball.r;
    } else if ( IsectRectRect(oR, oR, oT, oB, bL, bR, bT, bB) ) {
        hitDir = createVector(Ball.x, Ball.y).sub(createVector(oR, oT))
        isect = hitDir.mag() < Ball.r;
    }

    if ( isect ) {
        let dir = createVector(Ball.sideV, Ball.velocity);
        R = reflect(dir, hitDir.normalize());
        Ball.velocity = R.y;
        Ball.sideV = R.x;
        let oldY = Ball.y;
        Ball.y = oT - Ball.r;
        if (Ball.gravity < 2.0){
            Ball.gravity = Ball.gravity * 1.1;  
        } else {
            Ball.velocity = 0;
            Ball.y = oldY;
        }   
        return true;
    }
    return false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>