为了在移动的矩形和下降的圆形之间建立一个简单的碰撞检测系统,我想使它更加真实。
- 我想要解决的主要问题是检测圆形物体何时撞到矩形的角落,然后根据该角度进行圆形反弹。
代码:
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>
我希望看到落下的圆圈相对于它们撞击矩形的位置从矩形反弹。
如果圆圈击中角落,他们应该以不同的方式反弹而不是击中死角。
我将尝试提供一个解决方案,尽可能保留原始代码。该解决方案旨在成为问题中呈现的代码的演变。
向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;
}
并且可以计算入射矢量qazxsw poi的反射矢量qazxsw poi到表面R
的正常矢量(像台球一样的反射)的函数:
V
当N
与function reflect( V, N ) {
R = V.copy().sub(N.copy().mult(2.0 * V.dot(N)));
return R;
}
碰撞时,你将处理3种情况。
Ball
的顶部:Obstacle
Obstacle
的左边缘:IsectRectRect(oL, oR, oT, oB, Ball.x, Ball.x, bT, bB)
Obstacle
的右边缘:IsectRectRect(oL, oL, oT, oB, bL, bR, bT, bB)
在每种情况下,必须计算反射的法向量。这是从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;
}
条件
球的速度必须是矢量(XY分量),而不仅仅是一个数字。
从矩形的中心到圆圈获取矢量的分量,并根据矩形的尺寸进行检查:
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>
都不为零,则球可能会撞到一个角落,否则它可能会撞到一侧。
将每个符号乘以相应的半维:
// 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
是法线向量的组成部分,但仅当球的中心位于矩形之外时:
// 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 */
}
(请不要成熟的笑话)
这可确保球永远不会穿透矩形表面,从而提高碰撞的视觉质量:
(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;
ball.x += (ball.r - dc) * nx;
ball.y += (ball.r - dc) * ny;
// 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;