碰撞后改变球的方向

Changing direction of the ball after collision

我编写这段代码是为了演示一个基本的可视化 p5js 项目。在这里有 10 个不同大小和颜色的球在随机位置生成,在 canvas 中四处移动并可能相互碰撞。我不是在寻找弹性碰撞或“现实”碰撞物理学。我只是想让球改变到不同的方向(可以是随机的,只要它有效)并相应地工作。

这是我的代码:

class Ball {
  //create new ball using given arguments
  constructor(pos, vel, radius, color) {
    this.pos = pos;
    this.vel = vel;
    this.radius = radius;
    this.color = color;
  }
  //collision detection
  collide(check) {
    if (check == this) {
      return;
    }
    let relative = p5.Vector.sub(check.pos, this.pos);
    let dist = relative.mag() - (this.radius + check.radius);

    if (dist < 0) { //HELP HERE! <--
      this.vel.mult(-1);
      check.vel.mult(-1);
    }
  }

  //give life to the ball
  move() {
    this.pos.add(this.vel);

    if (this.pos.x < this.radius) {
      this.pos.x = this.radius;
      this.vel.x = -this.vel.x;
    }
    if (this.pos.x > width - this.radius) {
      this.pos.x = width - this.radius;
      this.vel.x = -this.vel.x;
    }
    if (this.pos.y < this.radius) {
      this.pos.y = this.radius;
      this.vel.y = -this.vel.y;
    }
    if (this.pos.y > height - this.radius) {
      this.pos.y = height - this.radius;
      this.vel.y = -this.vel.y;
    }
  }
  //show the ball on the canvas
  render() {
    fill(this.color);
    noStroke();
    ellipse(this.pos.x, this.pos.y, this.radius * 2);
  }
}

let balls = []; //stores all the balls

function setup() {
  createCanvas(window.windowWidth, window.windowHeight);
  let n = 10;
  //loop to create n balls
  for (i = 0; i < n; i++) {
    balls.push(
      new Ball(
        createVector(random(width), random(height)),
        p5.Vector.random2D().mult(random(5)),
        random(20, 50),
        color(random(255), random(255), random(255))
      )
    );
  }
}

function draw() {
  background(0);
  //loop to detect collision at all instances
  for (let i = 0; i < balls.length; i++) {
    for (let j = 0; j < i; j++) {
      balls[i].collide(balls[j]);
    }
  }
  //loop to render and move all balls
  for (let i = 0; i < balls.length; i++) {
    balls[i].move();
    balls[i].render();
  }
}

这里是一个 link 项目:https://editor.p5js.org/AdilBub/sketches/TNn2OREsN

我所需要的只是将球的方向改变为随机方向而不会卡住的碰撞。任何帮助,将不胜感激。我正在教孩子们这个程序,所以我只想要基本的碰撞,不一定要“现实”。

感谢任何帮助。谢谢。

您目前遇到的球被卡住的问题与随机生成重叠的球有关,这样在一次运动迭代后它们仍然重叠。当发生这种情况时,两个球将简单地在原地振荡,反复相互碰撞。您可以通过在添加新球之前检查碰撞来简单地防止这种情况:

class Ball {
  //create new ball using given arguments
  constructor(pos, vel, radius, color) {
    this.pos = pos;
    this.vel = vel;
    this.radius = radius;
    this.color = color;
  }
  
  isColliding(check) {
    if (check == this) {
      return;
    }
    let relative = p5.Vector.sub(check.pos, this.pos);
    let dist = relative.mag() - (this.radius + check.radius);
    return dist < 0;
  }
  
  //collision detection
  collide(check) {
    if (this.isColliding(check)) {
      this.vel.x *= -1;
      this.vel.y *= -1;
      check.vel.x *= -1;
      check.vel.y *= -1;
    }
  }

  //give life to the ball
  move() {
    this.pos.add(this.vel);

    if (this.pos.x < this.radius) {
      this.pos.x = this.radius;
      this.vel.x = -this.vel.x;
    }
    if (this.pos.x > width - this.radius) {
      this.pos.x = width - this.radius;
      this.vel.x = -this.vel.x;
    }
    if (this.pos.y < this.radius) {
      this.pos.y = this.radius;
      this.vel.y = -this.vel.y;
    }
    if (this.pos.y > height - this.radius) {
      this.pos.y = height - this.radius;
      this.vel.y = -this.vel.y;
    }
  }
  //show the ball on the canvas
  render() {
    fill(this.color);
    noStroke();
    ellipse(this.pos.x, this.pos.y, this.radius * 2);
  }
}

let balls = []; //stores all the balls

function setup() {
  createCanvas(500, 500);
  let n = 10;
  //loop to create n balls
  for (i = 0; i < n; i++) {
    let newBall =
      new Ball(
        createVector(random(width), random(height)),
        p5.Vector.random2D().mult(random(5)),
        random(20, 40),
        color(random(255), random(255), random(255))
      );
    let isOk = true;
    // check for collisions with existing balls
    for (let j = 0; j < balls.length; j++) {
      if (newBall.isColliding(balls[j])) {
        isOk = false;
        break;
      }
    }
    if (isOk) {
      balls.push(newBall);
    } else {
      // try again
      i--;
    }
  }
}

function draw() {
  background(0);
  //loop to detect collision at all instances
  for (let i = 0; i < balls.length; i++) {
    for (let j = 0; j < i; j++) {
      balls[i].collide(balls[j]);
    }
  }
  //loop to render and move all balls
  for (let i = 0; i < balls.length; i++) {
    balls[i].move();
    balls[i].render();
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>


也就是说,完全弹性碰撞(这意味着碰撞是瞬时的,不涉及由于变形和由此产生的热量排放而造成的能量损失)实际上很容易模拟。这是我在 OpenProcessing 上制作的教程,使用 p5.js 演示了必要的概念:Elastic Ball Collision Tutorial.

这是该教程中代码的最终版本:

const radius = 30;
const speed = 100;

let time;

let balls = []
let boundary = [];
let obstacles = [];

let paused = false;

function setup() {
    createCanvas(400, 400);
    angleMode(DEGREES);
    ellipseMode(RADIUS);

    boundary.push(createVector(60, 4));
    boundary.push(createVector(width - 4, 60));
    boundary.push(createVector(width - 60, height - 4));
    boundary.push(createVector(4, height - 60));

    obstacles.push(createVector(width / 2, height / 2));

    balls.push({
        pos: createVector(width * 0.25, height * 0.25),
        vel: createVector(speed, 0).rotate(random(0, 360))
    });
    balls.push({
        pos: createVector(width * 0.75, height * 0.75),
        vel: createVector(speed, 0).rotate(random(0, 360))
    });
    balls.push({
        pos: createVector(width * 0.25, height * 0.75),
        vel: createVector(speed, 0).rotate(random(0, 360))
    });

    time = millis();
}

function keyPressed() {
    if (key === "p") {
        paused = !paused;
        time = millis();
    }
}

function draw() {
    if (paused) {
        return;
    }
    
    deltaT = millis() - time;
    time = millis();

    background('dimgray');

    push();
    fill('lightgray');
    stroke('black');
    strokeWeight(2);
    beginShape();
    for (let v of boundary) {
        vertex(v.x, v.y);
    }
    endShape(CLOSE);
    pop();

    push();
    fill('dimgray');
    for (let obstacle of obstacles) {
        circle(obstacle.x, obstacle.y, radius);
    }
    pop();

    for (let i = 0; i < balls.length; i++) {
        let ball = balls[i];

        // update position
        ball.pos = createVector(
            min(max(0, ball.pos.x + ball.vel.x * (deltaT / 1000)), width),
            min(max(0, ball.pos.y + ball.vel.y * (deltaT / 1000)), height)
        );

        // check for collisions
        for (let i = 0; i < boundary.length; i++) {
            checkCollision(ball, boundary[i], boundary[(i + 1) % boundary.length]);
        }

        for (let obstacle of obstacles) {
            // Find the tangent plane that is perpendicular to a line from the obstacle to
            // the moving circle

            // A vector pointing in the direction of the moving object
            let dirVector = p5.Vector.sub(ball.pos, obstacle).normalize().mult(radius);

            // The point on the perimiter of the obstacle that is in the direction of the
            // moving object
            let p1 = p5.Vector.add(obstacle, dirVector);
            checkCollision(ball, p1, p5.Vector.add(p1, p5.Vector.rotate(dirVector, -90)));
        }
        
        // Check for collisions with other balls
        for (let j = 0; j < i; j++) {
            let other = balls[j];
            
            let distance = dist(ball.pos.x, ball.pos.y, other.pos.x, other.pos.y);
            if (distance / 2 < radius) {
                push();
                let midPoint = p5.Vector.add(ball.pos, other.pos).div(2);
                let boundaryVector = p5.Vector.sub(other.pos, ball.pos).rotate(-90);
                
                let v1Parallel = project(ball.vel, boundaryVector);
                let v2Parallel = project(other.vel, boundaryVector);
                
                let v1Perpendicular = p5.Vector.sub(ball.vel, v1Parallel);
                let v2Perpendicular = p5.Vector.sub(other.vel, v2Parallel);
                
                ball.vel = p5.Vector.add(v1Parallel, v2Perpendicular);
                other.vel = p5.Vector.add(v2Parallel, v1Perpendicular);
                
                let bounce = min(radius, 2 * radius - distance);
                ball.pos.add(p5.Vector.rotate(boundaryVector, -90).normalize().mult(bounce));
                other.pos.add(p5.Vector.rotate(boundaryVector, 90).normalize().mult(bounce));
                
                pop();
            }
        }
    }

    // Only draw balls after all position updates are complete
    for (let ball of balls) {
        circle(ball.pos.x, ball.pos.y, radius);
    }
}

function drawLine(origin, offset) {
    line(origin.x, origin.y, origin.x + offset.x, origin.y + offset.y);
}

// Handles collision with a plane given two points on the plane.
// It is assumed that given a vector from p1 to p2, roating that vector
// clockwise 90 degrees will give a vector pointing to the in-bounds side of the
// plane (i.e. a "normal").
function checkCollision(ball, p1, p2) {
    let boundaryVector = p5.Vector.sub(p2, p1);
    let objVector = p5.Vector.sub(ball.pos, p1);
    let angle = boundaryVector.angleBetween(objVector);
    let distance = objVector.mag() * sin(angle);

    if (distance <= radius) {
        // Collision
        let vParallel = project(ball.vel, boundaryVector);
        let vPerpendicular = p5.Vector.sub(ball.vel, vParallel);

        ball.vel = p5.Vector.add(vParallel, p5.Vector.mult(vPerpendicular, -1));

        let bounce = min(radius, (radius - distance) * 2);
        // If the ball has crossed over beyond the plane we want to offset it to be on
        // the in-bounds side of the plane.
        let bounceOffset = p5.Vector.rotate(boundaryVector, 90).normalize().mult(bounce);
        ball.pos.add(bounceOffset);
    }
}

// p5.Vector helpers
function project(vect1, vect2) {
    vect2 = p5.Vector.normalize(vect2);
    return p5.Vector.mult(vect2, p5.Vector.dot(vect1, vect2));
}

function reject(vect1, vect2) {
    return p5.Vector.sub(vect1, project(vect1, vect2));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>