编写球碰撞墙壁的动画,但有些球 (~5%) 直接穿过墙壁。使用 p5.js 共 JavaScript
Coding an animation of balls colliding against the walls but some balls (~5%) pass right through the walls. Using p5.js of JavaScript
问题如题。我使用以下 pdf 作为指南来计算碰撞后的速度:(第 2 页和第 3 页)
https://imada.sdu.dk/~rolf/Edu/DM815/E10/2dcollisions.pdf
考虑所有球的质量相同。我正在使用 p5.js 网页编辑器。
我对代码进行了彻底的注释,使其易于理解。有什么不明白的地方请问我。
另外,我认为主要问题一定出在 class 球的 move() 函数中,但我还是复制了整个代码,因为大多数时候问题出在我们意想不到的地方。
let sides=4 //no of sides of a regular polygon
let r // distance of the vertices of the polygon from center
let points=[] //
let arrayofLines=[]
let arrayofBalls=[]
let noofBalls=100
function setup() {
createCanvas(600, 600);
angleMode(DEGREES)
colorMode(HSB)
r=width/3
for (let i=1;i<=sides;i++)points.push(createRegularPoints(r,i*360/sides)) // creating vertices of regular polygon
createRegularPolygon() //drawing regular polygon
for(let i=0;i<noofBalls;i++)arrayofBalls.push(new ball()) // creating balls
}
function draw() {
background(20);
stroke(0)
strokeWeight(8)
for(let i=0;i<arrayofLines.length;i++)arrayofLines[i].show()
for(let b of arrayofBalls){
b.move()
b.show()
}
}
function createRegularPoints(r,a){
return [width/2+r*cos(a),height/2+r*sin(a)]
}
function createRegularPolygon(){
let i
for(i=0;i<points.length-1;i++)arrayofLines.push(new arrayofLineses(points[i][0],points[i][1],points[i+1][0],points[i+1][1])) //send x and y of i and i+1 point to create a line
arrayofLines.push(new arrayofLineses(points[i][0],points[i][1],points[0][0],points[0][1])) //last line is joint with the first line
}
class arrayofLineses {
constructor(x1,y1,x2,y2) {
this.p1=[x1,y1]
this.p2=[x2,y2]
}
show() {
push();
stroke(400);
strokeWeight(4);
line(this.p1[0], this.p1[1], this.p2[0], this.p2[1]);
pop();
}
}
class ball {
constructor() {
this.x = random(0, width);
this.y = random(0, height);
this.speedx = random(-2, 2);
this.speedy = random(-2, 2);
this.c = map(dist(0, 0, this.x, this.y), 0, sqrt(2) * height, 0, 360); //just mapping for color of balls
}
show() {
push();
noStroke();
fill(this.c, 80, 80);
circle(this.x, this.y, 5);
pop();
}
move() {
if (this.x <= 0 || this.x >= width) this.speedx *= -1; //collision against vertical walls
if (this.y <= 0 || this.y >= height) this.speedy *= -1; //collision against horizontal walls
let x1 = this.x; //these (x1,y1) and (x2,y2) now form a line in the direction of balls movment
let y1 = this.y;
let x2 = this.x + this.speedx;
let y2 = this.y + this.speedy;
for (let l of arrayofLines) { //checking collision against each line
let x3 = l.p1[0]; //again two end points of the line
let y3 = l.p1[1];
let x4 = l.p2[0];
let y4 = l.p2[1];
//Here I calculate the intersection between two lines using two point form(formula from wiki). So x and y are collision coordinates
let D = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
let x =
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / D;
let y =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / D;
// checking if the collision point x,y actually lies on the line segment
if (
dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1])
) {
//checking if the ball is close enough to line to be a called colliding
let xdis = x - this.x;
let ydis = y - this.y;
if (xdis * xdis + ydis * ydis < 5 * 5) {
//From here on, I used the mentioned pdf but doesn't use the vectors. Instead I create x and y for every vector mentioned.
//normal vector (perpendicular to line, so slope of normal= -1/(slope of line))
ydis = (l.p1[0] - l.p2[0]);
xdis = -(l.p1[1] - l.p2[1]);
let magn = sqrt(xdis * xdis + ydis * ydis);
xdis /= magn;
ydis /= magn;
let utx = -ydis;
let uty = xdis;
let v1n = xdis * this.speedx + ydis * this.speedy;
let v1t = utx * this.speedx + uty * this.speedy;
v1n = -v1n;
let v1nx = v1n * xdis;
let v1ny = v1n * ydis;
let v1tx = v1t * utx;
let v1ty = v1t * uty;
let vfx = v1nx + v1tx;
let vfy = v1ny + v1ty;
this.speedx = vfx;
this.speedy = vfy;
}
}
}
this.x += this.speedx;
this.y += this.speedy;
}
}
也许这会对你有所帮助
if(xPos < 0 || xPos > width) {
xSpeed = xSpeed * -1; // Invert xSpeed when xPos is less than 0 or greater than width
}
if(yPos < 0 || yPos > height) {
ySpeed = ySpeed * -1; // Invert ySpeed when yPos is less than 0 or greater than height
}
有关详细信息,请访问:
https://www.codecademy.com/courses/learn-p5js/lessons/p5js-animation/exercises/p5js-speed-and-direction
我认为主要问题源于您确定球轨迹与构成盒子的线之间的交点是否实际上位于构成盒子边界的线内的方法。您的代码似乎将框边界线的一个端点到交点的距离与框边界线的另一端到交点的距离相加,然后检查总距离是否小于或等于该边框的总长度:
/* end 1 to collision */ /* end 2 to collision */
if(dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
/* total box edge length. */
dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1])) {
// ...
}
不用说,这里计算的距离总和永远不会小于直线的长度,它总是大于或等于。每当您处理浮点数并且仅在两个计算值完全相等时才做某事时,您应该暂停一下,因为一点舍入误差都会导致问题。
幸运的是有一个更简单的方法来计算这个条件:只要碰撞点的x
坐标在盒子边缘的两个端点之间,那么你就有一个交点:
let minX = Math.min(l.p1[0], l.p2[0]);
let maxX = Math.max(l.p1[0], l.p2[0]);
if (x >= minX && x <= maxX) {
// ...
}
这是一个工作示例,其中包含一些其他调整以简化调试:
let sides = 4; //no of sides of a regular polygon
let r; // distance of the vertices of the polygon from center
let points = []; //
let arrayofLines = [];
let arrayofBalls = [];
let noofBalls = 4;
function setup() {
createCanvas(600, 600);
angleMode(DEGREES);
colorMode(HSB);
r = width / 3;
for (let i = 1; i <= sides; i++) {
points.push(createRegularPoints(r, (i * 360) / sides));
}
// creating vertices of regular polygon
createRegularPolygon(); //drawing regular polygon
for (let i = 0; i < noofBalls; i++) {
arrayofBalls.push(new ball()); // creating balls
}
}
function draw() {
background(20);
stroke(0);
strokeWeight(8);
for (let i = 0; i < arrayofLines.length; i++) arrayofLines[i].show();
for (let b of arrayofBalls) {
b.move();
b.show();
}
}
function createRegularPoints(r, a) {
return [width / 2 + r * cos(a), height / 2 + r * sin(a)];
}
function createRegularPolygon() {
let i;
for (i = 0; i < points.length - 1; i++)
arrayofLines.push(
new arrayofLineses(
points[i][0],
points[i][1],
points[i + 1][0],
points[i + 1][1]
)
); //send x and y of i and i+1 point to create a line
arrayofLines.push(
new arrayofLineses(points[i][0], points[i][1], points[0][0], points[0][1])
); //last line is joint with the first line
}
class arrayofLineses {
constructor(x1, y1, x2, y2) {
this.p1 = [x1, y1];
this.p2 = [x2, y2];
}
show() {
push();
stroke(400);
strokeWeight(4);
line(this.p1[0], this.p1[1], this.p2[0], this.p2[1]);
pop();
}
}
class ball {
constructor() {
// Generate only points inside the box
let theta = random(0, 360);
// Calculate the maximum distance from the center based on the polar equation for a square!
let maxR = r * Math.min(1 / abs(sin(theta + 45)), 1 / abs(cos(theta + 45))) / sqrt(2);
let offset = random(0, maxR);
let pos = createVector(offset, 0).rotate(theta);
this.x = pos.x + width / 2;
this.y = pos.y + height / 2;
// Pick a random direction, but a constant speed of 2
let omega = random(0, 360);
let vel = createVector(2, 0).rotate(omega);
this.speedx = vel.x;
this.speedy = vel.y;
this.collisions = [];
}
show() {
push();
noStroke();
fill('red');
circle(this.x, this.y, 5);
fill('lime');
for (const [x, y] of this.collisions) {
circle(x, y, 5);
}
pop();
}
move() {
if (this.x <= 0 || this.x >= width) {
this.speedx *= -1; //collision against vertical walls
}
if (this.y <= 0 || this.y >= height) {
this.speedy *= -1; //collision against horizontal walls
}
let x1 = this.x; //these (x1,y1) and (x2,y2) now form a line in the direction of balls movment
let y1 = this.y;
let x2 = this.x + this.speedx;
let y2 = this.y + this.speedy;
for (let l of arrayofLines) {
//checking collision against each line
let x3 = l.p1[0]; //again two end points of the line
let y3 = l.p1[1];
let x4 = l.p2[0];
let y4 = l.p2[1];
//Here I calculate the intersection between two lines using two point form(formula from wiki). So x and y are collision coordinates
let D = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
let x =
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / D;
let y =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / D;
// checking if the collision point x,y actually lies on the line segment
/*
dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1]) */
// instead of summing the distance between the collision points and each
// end point of the line, just see if it lies within the bounds of the
// line in the x direction.
let minX = Math.min(l.p1[0], l.p2[0]);
let maxX = Math.max(l.p1[0], l.p2[0]);
if (x >= minX && x <= maxX) {
// This was for debugging
// this.collisions.push([x, y]);
//checking if the ball is close enough to line to be a called colliding
let xdis = x - this.x;
let ydis = y - this.y;
if (xdis * xdis + ydis * ydis < 5 * 5) {
//From here on, I used the mentioned pdf but doesn't use the vectors. Instead I create x and y for every vector mentioned.
//normal vector (perpendicular to line, so slope of normal= -1/(slope of line))
ydis = l.p1[0] - l.p2[0];
xdis = -(l.p1[1] - l.p2[1]);
let magn = sqrt(xdis * xdis + ydis * ydis);
xdis /= magn;
ydis /= magn;
let utx = -ydis;
let uty = xdis;
let v1n = xdis * this.speedx + ydis * this.speedy;
let v1t = utx * this.speedx + uty * this.speedy;
v1n = -v1n;
let v1nx = v1n * xdis;
let v1ny = v1n * ydis;
let v1tx = v1t * utx;
let v1ty = v1t * uty;
let vfx = v1nx + v1tx;
let vfy = v1ny + v1ty;
this.speedx = vfx;
this.speedy = vfy;
}
}
}
this.x += this.speedx;
this.y += this.speedy;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
问题如题。我使用以下 pdf 作为指南来计算碰撞后的速度:(第 2 页和第 3 页)
https://imada.sdu.dk/~rolf/Edu/DM815/E10/2dcollisions.pdf
考虑所有球的质量相同。我正在使用 p5.js 网页编辑器。
我对代码进行了彻底的注释,使其易于理解。有什么不明白的地方请问我。
另外,我认为主要问题一定出在 class 球的 move() 函数中,但我还是复制了整个代码,因为大多数时候问题出在我们意想不到的地方。
let sides=4 //no of sides of a regular polygon
let r // distance of the vertices of the polygon from center
let points=[] //
let arrayofLines=[]
let arrayofBalls=[]
let noofBalls=100
function setup() {
createCanvas(600, 600);
angleMode(DEGREES)
colorMode(HSB)
r=width/3
for (let i=1;i<=sides;i++)points.push(createRegularPoints(r,i*360/sides)) // creating vertices of regular polygon
createRegularPolygon() //drawing regular polygon
for(let i=0;i<noofBalls;i++)arrayofBalls.push(new ball()) // creating balls
}
function draw() {
background(20);
stroke(0)
strokeWeight(8)
for(let i=0;i<arrayofLines.length;i++)arrayofLines[i].show()
for(let b of arrayofBalls){
b.move()
b.show()
}
}
function createRegularPoints(r,a){
return [width/2+r*cos(a),height/2+r*sin(a)]
}
function createRegularPolygon(){
let i
for(i=0;i<points.length-1;i++)arrayofLines.push(new arrayofLineses(points[i][0],points[i][1],points[i+1][0],points[i+1][1])) //send x and y of i and i+1 point to create a line
arrayofLines.push(new arrayofLineses(points[i][0],points[i][1],points[0][0],points[0][1])) //last line is joint with the first line
}
class arrayofLineses {
constructor(x1,y1,x2,y2) {
this.p1=[x1,y1]
this.p2=[x2,y2]
}
show() {
push();
stroke(400);
strokeWeight(4);
line(this.p1[0], this.p1[1], this.p2[0], this.p2[1]);
pop();
}
}
class ball {
constructor() {
this.x = random(0, width);
this.y = random(0, height);
this.speedx = random(-2, 2);
this.speedy = random(-2, 2);
this.c = map(dist(0, 0, this.x, this.y), 0, sqrt(2) * height, 0, 360); //just mapping for color of balls
}
show() {
push();
noStroke();
fill(this.c, 80, 80);
circle(this.x, this.y, 5);
pop();
}
move() {
if (this.x <= 0 || this.x >= width) this.speedx *= -1; //collision against vertical walls
if (this.y <= 0 || this.y >= height) this.speedy *= -1; //collision against horizontal walls
let x1 = this.x; //these (x1,y1) and (x2,y2) now form a line in the direction of balls movment
let y1 = this.y;
let x2 = this.x + this.speedx;
let y2 = this.y + this.speedy;
for (let l of arrayofLines) { //checking collision against each line
let x3 = l.p1[0]; //again two end points of the line
let y3 = l.p1[1];
let x4 = l.p2[0];
let y4 = l.p2[1];
//Here I calculate the intersection between two lines using two point form(formula from wiki). So x and y are collision coordinates
let D = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
let x =
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / D;
let y =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / D;
// checking if the collision point x,y actually lies on the line segment
if (
dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1])
) {
//checking if the ball is close enough to line to be a called colliding
let xdis = x - this.x;
let ydis = y - this.y;
if (xdis * xdis + ydis * ydis < 5 * 5) {
//From here on, I used the mentioned pdf but doesn't use the vectors. Instead I create x and y for every vector mentioned.
//normal vector (perpendicular to line, so slope of normal= -1/(slope of line))
ydis = (l.p1[0] - l.p2[0]);
xdis = -(l.p1[1] - l.p2[1]);
let magn = sqrt(xdis * xdis + ydis * ydis);
xdis /= magn;
ydis /= magn;
let utx = -ydis;
let uty = xdis;
let v1n = xdis * this.speedx + ydis * this.speedy;
let v1t = utx * this.speedx + uty * this.speedy;
v1n = -v1n;
let v1nx = v1n * xdis;
let v1ny = v1n * ydis;
let v1tx = v1t * utx;
let v1ty = v1t * uty;
let vfx = v1nx + v1tx;
let vfy = v1ny + v1ty;
this.speedx = vfx;
this.speedy = vfy;
}
}
}
this.x += this.speedx;
this.y += this.speedy;
}
}
也许这会对你有所帮助
if(xPos < 0 || xPos > width) {
xSpeed = xSpeed * -1; // Invert xSpeed when xPos is less than 0 or greater than width
}
if(yPos < 0 || yPos > height) {
ySpeed = ySpeed * -1; // Invert ySpeed when yPos is less than 0 or greater than height
}
有关详细信息,请访问: https://www.codecademy.com/courses/learn-p5js/lessons/p5js-animation/exercises/p5js-speed-and-direction
我认为主要问题源于您确定球轨迹与构成盒子的线之间的交点是否实际上位于构成盒子边界的线内的方法。您的代码似乎将框边界线的一个端点到交点的距离与框边界线的另一端到交点的距离相加,然后检查总距离是否小于或等于该边框的总长度:
/* end 1 to collision */ /* end 2 to collision */
if(dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
/* total box edge length. */
dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1])) {
// ...
}
不用说,这里计算的距离总和永远不会小于直线的长度,它总是大于或等于。每当您处理浮点数并且仅在两个计算值完全相等时才做某事时,您应该暂停一下,因为一点舍入误差都会导致问题。
幸运的是有一个更简单的方法来计算这个条件:只要碰撞点的x
坐标在盒子边缘的两个端点之间,那么你就有一个交点:
let minX = Math.min(l.p1[0], l.p2[0]);
let maxX = Math.max(l.p1[0], l.p2[0]);
if (x >= minX && x <= maxX) {
// ...
}
这是一个工作示例,其中包含一些其他调整以简化调试:
let sides = 4; //no of sides of a regular polygon
let r; // distance of the vertices of the polygon from center
let points = []; //
let arrayofLines = [];
let arrayofBalls = [];
let noofBalls = 4;
function setup() {
createCanvas(600, 600);
angleMode(DEGREES);
colorMode(HSB);
r = width / 3;
for (let i = 1; i <= sides; i++) {
points.push(createRegularPoints(r, (i * 360) / sides));
}
// creating vertices of regular polygon
createRegularPolygon(); //drawing regular polygon
for (let i = 0; i < noofBalls; i++) {
arrayofBalls.push(new ball()); // creating balls
}
}
function draw() {
background(20);
stroke(0);
strokeWeight(8);
for (let i = 0; i < arrayofLines.length; i++) arrayofLines[i].show();
for (let b of arrayofBalls) {
b.move();
b.show();
}
}
function createRegularPoints(r, a) {
return [width / 2 + r * cos(a), height / 2 + r * sin(a)];
}
function createRegularPolygon() {
let i;
for (i = 0; i < points.length - 1; i++)
arrayofLines.push(
new arrayofLineses(
points[i][0],
points[i][1],
points[i + 1][0],
points[i + 1][1]
)
); //send x and y of i and i+1 point to create a line
arrayofLines.push(
new arrayofLineses(points[i][0], points[i][1], points[0][0], points[0][1])
); //last line is joint with the first line
}
class arrayofLineses {
constructor(x1, y1, x2, y2) {
this.p1 = [x1, y1];
this.p2 = [x2, y2];
}
show() {
push();
stroke(400);
strokeWeight(4);
line(this.p1[0], this.p1[1], this.p2[0], this.p2[1]);
pop();
}
}
class ball {
constructor() {
// Generate only points inside the box
let theta = random(0, 360);
// Calculate the maximum distance from the center based on the polar equation for a square!
let maxR = r * Math.min(1 / abs(sin(theta + 45)), 1 / abs(cos(theta + 45))) / sqrt(2);
let offset = random(0, maxR);
let pos = createVector(offset, 0).rotate(theta);
this.x = pos.x + width / 2;
this.y = pos.y + height / 2;
// Pick a random direction, but a constant speed of 2
let omega = random(0, 360);
let vel = createVector(2, 0).rotate(omega);
this.speedx = vel.x;
this.speedy = vel.y;
this.collisions = [];
}
show() {
push();
noStroke();
fill('red');
circle(this.x, this.y, 5);
fill('lime');
for (const [x, y] of this.collisions) {
circle(x, y, 5);
}
pop();
}
move() {
if (this.x <= 0 || this.x >= width) {
this.speedx *= -1; //collision against vertical walls
}
if (this.y <= 0 || this.y >= height) {
this.speedy *= -1; //collision against horizontal walls
}
let x1 = this.x; //these (x1,y1) and (x2,y2) now form a line in the direction of balls movment
let y1 = this.y;
let x2 = this.x + this.speedx;
let y2 = this.y + this.speedy;
for (let l of arrayofLines) {
//checking collision against each line
let x3 = l.p1[0]; //again two end points of the line
let y3 = l.p1[1];
let x4 = l.p2[0];
let y4 = l.p2[1];
//Here I calculate the intersection between two lines using two point form(formula from wiki). So x and y are collision coordinates
let D = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
let x =
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / D;
let y =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / D;
// checking if the collision point x,y actually lies on the line segment
/*
dist(x, y, l.p1[0], l.p1[1]) + dist(x, y, l.p2[0], l.p2[1]) <=
dist(l.p1[0], l.p1[1], l.p2[0], l.p2[1]) */
// instead of summing the distance between the collision points and each
// end point of the line, just see if it lies within the bounds of the
// line in the x direction.
let minX = Math.min(l.p1[0], l.p2[0]);
let maxX = Math.max(l.p1[0], l.p2[0]);
if (x >= minX && x <= maxX) {
// This was for debugging
// this.collisions.push([x, y]);
//checking if the ball is close enough to line to be a called colliding
let xdis = x - this.x;
let ydis = y - this.y;
if (xdis * xdis + ydis * ydis < 5 * 5) {
//From here on, I used the mentioned pdf but doesn't use the vectors. Instead I create x and y for every vector mentioned.
//normal vector (perpendicular to line, so slope of normal= -1/(slope of line))
ydis = l.p1[0] - l.p2[0];
xdis = -(l.p1[1] - l.p2[1]);
let magn = sqrt(xdis * xdis + ydis * ydis);
xdis /= magn;
ydis /= magn;
let utx = -ydis;
let uty = xdis;
let v1n = xdis * this.speedx + ydis * this.speedy;
let v1t = utx * this.speedx + uty * this.speedy;
v1n = -v1n;
let v1nx = v1n * xdis;
let v1ny = v1n * ydis;
let v1tx = v1t * utx;
let v1ty = v1t * uty;
let vfx = v1nx + v1tx;
let vfy = v1ny + v1ty;
this.speedx = vfx;
this.speedy = vfy;
}
}
}
this.x += this.speedx;
this.y += this.speedy;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>