Углы столкновения в p5.js - PullRequest
2 голосов
/ 29 марта 2019

Резюме:

В попытке создать простую систему обнаружения столкновений между движущимся прямоугольником и падающим кругом, я хотел бы сделать ее более реалистичной.

Основной вопрос:

- Главное, что я хотел бы решить, это обнаружение, когда объект окружности попадает в угол прямоугольника, и, в свою очередь, отскок окружности от этого угла.

Код:

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 ]

3 голосов
/ 29 марта 2019

Предпосылка

Скорость шара должна быть вектором (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 голос
/ 29 марта 2019

Я постараюсь представить решение, максимально отличающееся от исходного кода.Решением является эволюция кода, представленного в вопросе.

Добавьте боковое движение (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;
}

КогдаBall сталкивается с Obstacle, тогда вам нужно разобраться с 3 ситуациями.

  1. Мяч полностью попадает в вершину Obstacle: IsectRectRect(oL, oR, oT, oB, Ball.x, Ball.x, bT, bB)
  2. Мяч попадает в левый край Obstacle: IsectRectRect(oL, oL, oT, oB, bL, bR, bT, bB)
  3. Мяч попадает в правый край Obstacle: IsectRectRect(oR, oR, oT, oB, bL, bR, bT, bB)

В каждом случае нормальный вектордля отражения должен быть рассчитан.Это вектор от вершины или края Obstacle к центру Ball.
. Используйте функцию reflect, чтобы отразить Ball в 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>
...