圆圈 - 圆圈碰撞响应(球重叠)

时间:2018-02-04 00:03:37

标签: javascript collision geometry p5.js

我正在尝试使用p5.js在JavaScript中构建类似的东西:

Circle - circle collision

我能够检测到两个圆圈之间的碰撞,并且我正在尝试使用维基百科的这些(二维弹性碰撞)公式来计算新的速度: Elastic collision formulas 这些角度为theta和phi: Collision angles

问题似乎是当我计算出新的速度时,球已经碰撞/重叠并且它们被卡住了。我认为我应该做的是根据圆圈重叠的距离计算一个新的圆圈位置。不幸的是我不知道该怎么做。我也认为我计算的方式过于复杂和低效。 这就是我的碰撞处理代码:

  // takes ball object as input, sets speedvector to new x,y speed
  collision(other) {
    var newSpeed = createVector();
    var phi = Math.atan((this.pos.y - other.pos.y) / this.pos.x - other.pos.x);
    var theta1 = this.speed.heading();
    var theta2 = other.speed.heading();

    newSpeed.x = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.cos(phi) - this.speed.mag() * Math.sin(theta1 - phi) * Math.sin(phi);
    newSpeed.y = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.sin(phi) + this.speed.mag() * Math.sin(theta1 - phi) * Math.cos(phi);

    this.speed.x = newSpeed.x;
    this.speed.y = newSpeed.y;
  }

完整的代码示例:

var balls = [];
var numOfBalls = 5;
var maxSpeed = 2;


function setup() {
  createCanvas(500, 500);
  
  for (var i = 0; i < numOfBalls; i++) {
    var ball = new Ball(30);
    balls.push(ball);
  }
}

function draw() {
  background(0);
  for (var i = 0; i < balls.length; i++) {
    balls[i].move();
  }
  for (var i = 0; i < balls.length; i++) {
    balls[i].show();
  }
}

class Ball {
  constructor(radius) {
    this.radius = radius;
    this.pos = this.pickLocation();
    this.speed = createVector(random(-maxSpeed, maxSpeed), random(-maxSpeed, maxSpeed));
    this.mass = 1;
  }
  
  pickLocation() {
    //spawn within canvas
    var xOption = random(this.radius, width - this.radius);
    var yOption = random(this.radius, height - this.radius);
    
    // check whether spawning on this location doesn't overlap other circles
    for(var i = 0; i < balls.length; i++) {
      // don't check for current circle
      if(balls[i] != this) {
        // get distance to other circle
        var d = dist(xOption, yOption, balls[i].pos.x, balls[i].pos.y);
        // check whether overlapping
        if (d <= this.radius + balls[i].radius) {
          // generate new location and rerun check
          console.log("overlapping another circle, trying new location");
          var xOption = random(this.radius, width - this.radius);
          var yOption = random(this.radius, height - this.radius);
          i = -1;
        }
      }
    }
    return(createVector(xOption, yOption));
  }
  
  move() {
    for (var i = 0; i < balls.length; i++) {
      if(balls[i] != this) {
        var d = dist(this.pos.x, this.pos.y, balls[i].pos.x, balls[i].pos.y);
        if(d < this.radius + balls[i].radius) {
          this.collision(balls[i]);
        }
      }
    }
    
    if(this.pos.x - this.radius < 0 || this.pos.x + this.radius > width) {
      this.speed.x *= -1;
    }
    if(this.pos.y - this.radius < 0 || this.pos.y + this.radius > height) {
      this.speed.y *= -1;
    }
    
    this.pos.x += this.speed.x;
    this.pos.y += this.speed.y;
  }
  
  // takes ball object as input, sets speedvector to new x,y speed
  collision(other) {
    var newSpeed = createVector();
    var phi = Math.atan((this.pos.y - other.pos.y) / this.pos.x - other.pos.x);
    var theta1 = this.speed.heading();
    var theta2 = other.speed.heading();
    
    newSpeed.x = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.cos(phi) - this.speed.mag() * Math.sin(theta1 - phi) * Math.sin(phi);
    newSpeed.y = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.sin(phi) + this.speed.mag() * Math.sin(theta1 - phi) * Math.cos(phi);
    
    this.speed.x = newSpeed.x;
    this.speed.y = newSpeed.y;
  }
  
  show() {
    fill(200, 100);
    noStroke();
    ellipse(this.pos.x, this.pos.y, this.radius * 2);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

1 个答案:

答案 0 :(得分:0)

我编写了这段代码。我完全更改了碰撞功能,但是效果很好! 另外,限制位置,以免卡在墙上。

几个月前我做了确切的事情,所以你可以看看 here

我还将画布划分为网格,这样比循环遍历每个球要有效得多。

在落后之前,我的球可以打约4000个球

碰撞函数的工作方式就像牛顿的摇篮。这两个对象会改变速度(正在移动的球A击中静止的球B,现在球A不动,球B以相同的速度运动)。

var balls = [];
var numOfBalls = 5;
var maxSpeed = 2;


function setup() {
  createCanvas(500, 500);
  
  for (var i = 0; i < numOfBalls; i++) {
    var ball = new Ball(30);
    balls.push(ball);
  }
}

function draw() {
  background(0);
  for (var i = 0; i < balls.length; i++) {
    balls[i].move();
  }
  for (var i = 0; i < balls.length; i++) {
    balls[i].show();
  }
}

class Ball {
  constructor(radius) {
    this.radius = radius;
    this.pos = this.pickLocation();
    this.speed = createVector(random(-maxSpeed, maxSpeed), random(-maxSpeed, maxSpeed));
    this.mass = 1;
  }
  
  pickLocation() {
    //spawn within canvas
    var xOption = random(this.radius, width - this.radius);
    var yOption = random(this.radius, height - this.radius);
    
    // check whether spawning on this location doesn't overlap other circles
    for(var i = 0; i < balls.length; i++) {
      // don't check for current circle
      if(balls[i] != this) {
        // get distance to other circle
        var d = dist(xOption, yOption, balls[i].pos.x, balls[i].pos.y);
        // check whether overlapping
        if (d <= this.radius + balls[i].radius) {
          // generate new location and rerun check
          console.log("overlapping another circle, trying new location");
          xOption = random(this.radius, width - this.radius);
          yOption = random(this.radius, height - this.radius);
          i = -1;
        }
      }
    }
    return(createVector(xOption, yOption));
  }
  
  move() {
    for (var i = 0; i < balls.length; i++) {
      if(balls[i] != this) {
        var d = dist(this.pos.x, this.pos.y, balls[i].pos.x, balls[i].pos.y);
        if(d < this.radius + balls[i].radius) {
          this.collision(balls[i]);
        }
      }
    }
    
    if(this.pos.x - this.radius < 0 || this.pos.x + this.radius > width) {
      this.speed.x *= -1;

    }
    if(this.pos.y - this.radius < 0 || this.pos.y + this.radius > height) {
      this.speed.y *= -1;

    }
    
    this.pos.x += this.speed.x;
    this.pos.y += this.speed.y;
    this.pos.x = constrain(this.pos.x,0,500)
    this.pos.y = constrain(this.pos.y,0,500)


  }

  // takes ball object as input, sets speedvector to new x,y speed
  collision(other) {
    this.v1 = 0;
    this.v2 = 0;
    this.direction = p5.Vector.sub(other.pos, this.pos)
    this.dist = this.direction.mag()
    this.direction.normalize();
    //this is 60 because that is the radius you give them times two
    this.correction = 60-this.dist;
    this.pos.sub(p5.Vector.mult(this.direction,this.correction/2))
    other.pos.add(p5.Vector.mult(this.direction,this.correction/2))
    this.v1 = this.direction.dot(this.speed)
    this.v2 = this.direction.dot(other.speed)
    this.direction.mult(this.v1-this.v2)
    this.speed.sub(p5.Vector.mult(this.direction,1));
    other.speed.add(p5.Vector.mult(this.direction,1))

    
  }
  
  show() {
    fill(200, 100);
    noStroke();
    ellipse(this.pos.x, this.pos.y, this.radius * 2);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>