为什么这个圆圈内的球不能正常弹跳?
Why is this ball inside a circle not bouncing properly?
请看这个Fiddle:https://jsfiddle.net/sfarbota/wd5aa1wv/2/
我正在尝试让球以正确的角度在圆圈内弹跳而不损失速度。我想我已经关闭了碰撞检测,但我面临两个问题:
- 球每次弹跳都会减慢速度,直到最终停止。
- 弹跳的角度似乎不正确。
这部分基于此处给出的答案: 但我不得不从 Java 翻译过来,并且还跳过了他们示例中似乎无关紧要的几行。
代码如下:
Java脚本:
function getBall(xVal, yVal, dxVal, dyVal, rVal, colorVal) {
var ball = {
x: xVal,
lastX: xVal,
y: yVal,
lastY: yVal,
dx: dxVal,
dy: dyVal,
r: rVal,
color: colorVal,
normX: 0,
normY: 0
};
return ball;
}
var canvas = document.getElementById("myCanvas");
var xLabel = document.getElementById("x");
var yLabel = document.getElementById("y");
var dxLabel = document.getElementById("dx");
var dyLabel = document.getElementById("dy");
var vLabel = document.getElementById("v");
var normXLabel = document.getElementById("normX");
var normYLabel = document.getElementById("normY");
var ctx = canvas.getContext("2d");
var containerR = 200;
canvas.width = containerR * 2;
canvas.height = containerR * 2;
canvas.style["border-radius"] = containerR + "px";
var balls = [
//getBall(canvas.width / 2, canvas.height - 30, 2, -2, 20, "#0095DD"),
//getBall(canvas.width / 3, canvas.height - 50, 3, -3, 30, "#DD9500"),
//getBall(canvas.width / 4, canvas.height - 60, -3, 4, 10, "#00DD95"),
getBall(canvas.width / 2, canvas.height / 5, -1.5, 3, 40, "#DD0095")
];
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < balls.length; i++) {
var curBall = balls[i];
ctx.beginPath();
ctx.arc(curBall.x, curBall.y, curBall.r, 0, Math.PI * 2);
ctx.fillStyle = curBall.color;
ctx.fill();
ctx.closePath();
curBall.lastX = curBall.x;
curBall.lastY = curBall.y;
curBall.x += curBall.dx;
curBall.y += curBall.dy;
if (containerR <= curBall.r + Math.sqrt(Math.pow(curBall.x - containerR, 2) + Math.pow(curBall.y - containerR, 2))) {
curBall.normX = (curBall.x + curBall.r) - (containerR);
curBall.normY = (curBall.y + curBall.r) - (containerR);
var normD = Math.sqrt(Math.pow(curBall.x, 2) + Math.pow(curBall.y, 2));
if (normD == 0)
normD = 1;
curBall.normX /= normD;
curBall.normY /= normD;
var dotProduct = (curBall.dx * curBall.normX) + (curBall.dy * curBall.normY);
curBall.dx = -2 * dotProduct * curBall.normX;
curBall.dy = -2 * dotProduct * curBall.normY;
}
xLabel.innerText = "x: " + curBall.x;
yLabel.innerText = "y: " + curBall.y;
dxLabel.innerText = "dx: " + curBall.dx;
dyLabel.innerText = "dy: " + curBall.dy;
vLabel.innerText = "v: " + curBall.dy / curBall.dx;
normXLabel.innerText = "normX: " + curBall.normX;
normYLabel.innerText = "normY: " + curBall.normY;
}
}
setInterval(draw, 10);
HTML:
<canvas id="myCanvas"></canvas>
<div id="x"></div>
<div id="y"></div>
<div id="dx"></div>
<div id="dy"></div>
<div id="v"></div>
<div id="normX"></div>
<div id="normY"></div>
CSS:
canvas { background: #eee; }
我的数学很生疏,所以我不太确定如何仅使用点积来计算球的新轨迹,但我相信您可以使用相关的三角函数来计算它:使用 atan2
计算碰撞点的角度和当前轨迹角度,使用这两个计算新角度,一对 sin
和 cos
乘以速度得到新的x/y 速度。
jsFiddle: https://jsfiddle.net/jacquesc/wd5aa1wv/6/
重要的部分是:
var dx = curBall.x - containerR;
var dy = curBall.y - containerR;
if (Math.sqrt(dx * dx + dy * dy) >= containerR - curBall.r) {
// current speed
var v = Math.sqrt(curBall.dx * curBall.dx + curBall.dy * curBall.dy);
// Angle from center of large circle to center of small circle,
// which is the same as angle from center of large cercle
// to the collision point
var angleToCollisionPoint = Math.atan2(-dy, dx);
// Angle of the current movement
var oldAngle = Math.atan2(-curBall.dy, curBall.dx);
// New angle
var newAngle = 2 * angleToCollisionPoint - oldAngle;
// new x/y speeds, using current speed and new angle
curBall.dx = -v * Math.cos(newAngle);
curBall.dy = v * Math.sin(newAngle);
}
另请注意,我从 setInterval
切换到 requestAnimationFrame
,这将确保每帧不超过一次更新。理想情况下,您希望根据自上次更新以来经过的实际时间来计算移动,而不是依赖它始终相同。
更新
使用点积:
jsFiddle: https://jsfiddle.net/jacquesc/wd5aa1wv/9/
var dx = curBall.x - containerR;
var dy = curBall.y - containerR;
var distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
if (distanceFromCenter >= containerR - curBall.r) {
var normalMagnitude = distanceFromCenter;
var normalX = dx / normalMagnitude;
var normalY = dy / normalMagnitude;
var tangentX = -normalY;
var tangentY = normalX;
var normalSpeed = -(normalX * curBall.dx + normalY * curBall.dy);
var tangentSpeed = tangentX * curBall.dx + tangentY * curBall.dy;
curBall.dx = normalSpeed * normalX + tangentSpeed * tangentX;
curBall.dy = normalSpeed * normalY + tangentSpeed * tangentY;
}
这是@jcaron 对点积代码的小升级。他做的速度向量反射很完美,但是越过边界会改变位置,没有考虑弹跳过程中的运动。
下面的代码将考虑球在击中边界之前每帧移动的距离,并根据弹跳前后的移动计算新位置。 https://jsfiddle.net/vm3wLk0z/
@jcaron代码和升级版的区别在球速越高的时候越明显。
function getBall(xVal, yVal, dxVal, dyVal, rVal, colorVal) {
var ball = {
x: xVal,
lastX: xVal,
y: yVal,
lastY: yVal,
dx: dxVal,
dy: dyVal,
r: rVal,
color: colorVal,
normX: 0,
normY: 0
};
return ball;
}
function circleLineInters (r, h, k, m, n) {
// circle: (x - h)^2 + (y - k)^2 = r^2
// line: y = m * x + n
// r: circle radius
// h: x value of circle centre
// k: y value of circle centre
// m: slope
// n: y-intercept
// get a, b, c values
var a = 1 + Math.pow(m,2);
var b = -h * 2 + (m * (n - k)) * 2;
var c = Math.pow(h,2) + Math.pow(n - k,2) - Math.pow(r,2);
// get discriminant
var d = Math.pow(b,2) - 4 * a * c;
if (d >= 0) {
// insert into quadratic formula
var intersections = [
(-b + Math.sqrt(Math.pow(b,2) - 4 * a * c)) / (2 * a),
(-b - Math.sqrt(Math.pow(b,2) - 4 * a * c)) / (2 * a)
];
if (d == 0) {
// only 1 intersection
return [intersections[0]];
}
return intersections;
}
// no intersection
return [];
}
var canvas = document.getElementById("myCanvas");
var xLabel = document.getElementById("x");
var yLabel = document.getElementById("y");
var dxLabel = document.getElementById("dx");
var dyLabel = document.getElementById("dy");
var ctx = canvas.getContext("2d");
var containerR = 200;
canvas.width = containerR * 2;
canvas.height = containerR * 2;
canvas.style["border-radius"] = containerR + "px";
var balls = [
//getBall(canvas.width / 2, canvas.height - 30, 2, -2, 20, "#0095DD"),
//getBall(canvas.width / 3, canvas.height - 50, 3, -3, 30, "#DD9500"),
//getBall(canvas.width / 4, canvas.height - 60, -3, 4, 10, "#00DD95"),
getBall(canvas.width / 2, canvas.height / 5, -2, 26, 40, "#DD0095")
];
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < balls.length; i++) {
var curBall = balls[i];
ctx.beginPath();
ctx.arc(curBall.x, curBall.y, curBall.r, 0, Math.PI * 2);
ctx.fillStyle = curBall.color;
ctx.fill();
ctx.closePath();
// move
curBall.lastX = curBall.x;
curBall.lastY = curBall.y;
if (curBall.xt) { // bounce
curBall.x = curBall.xt;
curBall.xt = false;
} else curBall.x += curBall.dx;
if (curBall.yt) { // bounce
curBall.y = curBall.yt;
curBall.yt = false;
} else curBall.y += curBall.dy;
// bounce
var nextx = curBall.x + curBall.dx,
nexty = curBall.y + curBall.dy;
var ndx = nextx - containerR;
var ndy = nexty - containerR;
var distanceFromCenter = Math.sqrt(ndx * ndx + ndy * ndy);
var rad = containerR - curBall.r;
if (distanceFromCenter >= rad) {
var s = Math.sqrt(curBall.dx * curBall.dx + curBall.dy * curBall.dy);
// calc collision point
// intersetion between line [(x,y)(x+dx,y+dx)]
// and circle [r = rad, c = (R,R)]
// m = rise = y2-y1/x2-x1 = ys/xs
var m1 = curBall.dy / curBall.dx;
// y = mx+n ... n = y-mx
var n1 = nexty - m1 * nextx;
var inters = circleLineInters(rad, containerR, containerR, m1, n1);
// possible intersections 0,1,2
// 0 inters can't hit, do nothing
// 1 inters tangent, only possible outside
if (inters.length == 2) { // line crosses the circle
var hitx = inters[0];
// choose inters x using the trajetory direction
if (curBall.dx < 0) hitx = inters[1];
// calc hity with linear formula y = mx + n
var hity = m1 * hitx + n1;
curBall.xt = hitx;
curBall.yt = hity;
//update speed vectors
var dx = curBall.xt - containerR;
var dy = curBall.yt - containerR;
var df = Math.sqrt(dx * dx + dy * dy);
var normalX = dx / df;
var normalY = dy / df;
var tangentX = -normalY;
var tangentY = normalX;
var normalSpeed = -(normalX * curBall.dx + normalY * curBall.dy);
var tangentSpeed = tangentX * curBall.dx + tangentY * curBall.dy;
curBall.dx = normalSpeed * normalX + tangentSpeed * tangentX;
curBall.dy = normalSpeed * normalY + tangentSpeed * tangentY;
// move cell to reflected position
var ra = Math.atan2(curBall.dy, curBall.dx);
var cdx = hitx - curBall.x;
var cdy = hity - curBall.y;
var collDist = Math.sqrt(cdx * cdx + cdy * cdy);
var rd = s - collDist;
curBall.xt = curBall.xt + rd * Math.cos(ra);
curBall.yt = curBall.yt + rd * Math.sin(ra);
}
}
}
requestAnimationFrame(draw);
}
draw();
canvas {
background: #eee;
border-radius: 50%;
}
<canvas id="myCanvas"></canvas>
请看这个Fiddle:https://jsfiddle.net/sfarbota/wd5aa1wv/2/
我正在尝试让球以正确的角度在圆圈内弹跳而不损失速度。我想我已经关闭了碰撞检测,但我面临两个问题:
- 球每次弹跳都会减慢速度,直到最终停止。
- 弹跳的角度似乎不正确。
这部分基于此处给出的答案: 但我不得不从 Java 翻译过来,并且还跳过了他们示例中似乎无关紧要的几行。
代码如下:
Java脚本:
function getBall(xVal, yVal, dxVal, dyVal, rVal, colorVal) {
var ball = {
x: xVal,
lastX: xVal,
y: yVal,
lastY: yVal,
dx: dxVal,
dy: dyVal,
r: rVal,
color: colorVal,
normX: 0,
normY: 0
};
return ball;
}
var canvas = document.getElementById("myCanvas");
var xLabel = document.getElementById("x");
var yLabel = document.getElementById("y");
var dxLabel = document.getElementById("dx");
var dyLabel = document.getElementById("dy");
var vLabel = document.getElementById("v");
var normXLabel = document.getElementById("normX");
var normYLabel = document.getElementById("normY");
var ctx = canvas.getContext("2d");
var containerR = 200;
canvas.width = containerR * 2;
canvas.height = containerR * 2;
canvas.style["border-radius"] = containerR + "px";
var balls = [
//getBall(canvas.width / 2, canvas.height - 30, 2, -2, 20, "#0095DD"),
//getBall(canvas.width / 3, canvas.height - 50, 3, -3, 30, "#DD9500"),
//getBall(canvas.width / 4, canvas.height - 60, -3, 4, 10, "#00DD95"),
getBall(canvas.width / 2, canvas.height / 5, -1.5, 3, 40, "#DD0095")
];
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < balls.length; i++) {
var curBall = balls[i];
ctx.beginPath();
ctx.arc(curBall.x, curBall.y, curBall.r, 0, Math.PI * 2);
ctx.fillStyle = curBall.color;
ctx.fill();
ctx.closePath();
curBall.lastX = curBall.x;
curBall.lastY = curBall.y;
curBall.x += curBall.dx;
curBall.y += curBall.dy;
if (containerR <= curBall.r + Math.sqrt(Math.pow(curBall.x - containerR, 2) + Math.pow(curBall.y - containerR, 2))) {
curBall.normX = (curBall.x + curBall.r) - (containerR);
curBall.normY = (curBall.y + curBall.r) - (containerR);
var normD = Math.sqrt(Math.pow(curBall.x, 2) + Math.pow(curBall.y, 2));
if (normD == 0)
normD = 1;
curBall.normX /= normD;
curBall.normY /= normD;
var dotProduct = (curBall.dx * curBall.normX) + (curBall.dy * curBall.normY);
curBall.dx = -2 * dotProduct * curBall.normX;
curBall.dy = -2 * dotProduct * curBall.normY;
}
xLabel.innerText = "x: " + curBall.x;
yLabel.innerText = "y: " + curBall.y;
dxLabel.innerText = "dx: " + curBall.dx;
dyLabel.innerText = "dy: " + curBall.dy;
vLabel.innerText = "v: " + curBall.dy / curBall.dx;
normXLabel.innerText = "normX: " + curBall.normX;
normYLabel.innerText = "normY: " + curBall.normY;
}
}
setInterval(draw, 10);
HTML:
<canvas id="myCanvas"></canvas>
<div id="x"></div>
<div id="y"></div>
<div id="dx"></div>
<div id="dy"></div>
<div id="v"></div>
<div id="normX"></div>
<div id="normY"></div>
CSS:
canvas { background: #eee; }
我的数学很生疏,所以我不太确定如何仅使用点积来计算球的新轨迹,但我相信您可以使用相关的三角函数来计算它:使用 atan2
计算碰撞点的角度和当前轨迹角度,使用这两个计算新角度,一对 sin
和 cos
乘以速度得到新的x/y 速度。
jsFiddle: https://jsfiddle.net/jacquesc/wd5aa1wv/6/
重要的部分是:
var dx = curBall.x - containerR;
var dy = curBall.y - containerR;
if (Math.sqrt(dx * dx + dy * dy) >= containerR - curBall.r) {
// current speed
var v = Math.sqrt(curBall.dx * curBall.dx + curBall.dy * curBall.dy);
// Angle from center of large circle to center of small circle,
// which is the same as angle from center of large cercle
// to the collision point
var angleToCollisionPoint = Math.atan2(-dy, dx);
// Angle of the current movement
var oldAngle = Math.atan2(-curBall.dy, curBall.dx);
// New angle
var newAngle = 2 * angleToCollisionPoint - oldAngle;
// new x/y speeds, using current speed and new angle
curBall.dx = -v * Math.cos(newAngle);
curBall.dy = v * Math.sin(newAngle);
}
另请注意,我从 setInterval
切换到 requestAnimationFrame
,这将确保每帧不超过一次更新。理想情况下,您希望根据自上次更新以来经过的实际时间来计算移动,而不是依赖它始终相同。
更新
使用点积:
jsFiddle: https://jsfiddle.net/jacquesc/wd5aa1wv/9/
var dx = curBall.x - containerR;
var dy = curBall.y - containerR;
var distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
if (distanceFromCenter >= containerR - curBall.r) {
var normalMagnitude = distanceFromCenter;
var normalX = dx / normalMagnitude;
var normalY = dy / normalMagnitude;
var tangentX = -normalY;
var tangentY = normalX;
var normalSpeed = -(normalX * curBall.dx + normalY * curBall.dy);
var tangentSpeed = tangentX * curBall.dx + tangentY * curBall.dy;
curBall.dx = normalSpeed * normalX + tangentSpeed * tangentX;
curBall.dy = normalSpeed * normalY + tangentSpeed * tangentY;
}
这是@jcaron 对点积代码的小升级。他做的速度向量反射很完美,但是越过边界会改变位置,没有考虑弹跳过程中的运动。
下面的代码将考虑球在击中边界之前每帧移动的距离,并根据弹跳前后的移动计算新位置。 https://jsfiddle.net/vm3wLk0z/
@jcaron代码和升级版的区别在球速越高的时候越明显。
function getBall(xVal, yVal, dxVal, dyVal, rVal, colorVal) {
var ball = {
x: xVal,
lastX: xVal,
y: yVal,
lastY: yVal,
dx: dxVal,
dy: dyVal,
r: rVal,
color: colorVal,
normX: 0,
normY: 0
};
return ball;
}
function circleLineInters (r, h, k, m, n) {
// circle: (x - h)^2 + (y - k)^2 = r^2
// line: y = m * x + n
// r: circle radius
// h: x value of circle centre
// k: y value of circle centre
// m: slope
// n: y-intercept
// get a, b, c values
var a = 1 + Math.pow(m,2);
var b = -h * 2 + (m * (n - k)) * 2;
var c = Math.pow(h,2) + Math.pow(n - k,2) - Math.pow(r,2);
// get discriminant
var d = Math.pow(b,2) - 4 * a * c;
if (d >= 0) {
// insert into quadratic formula
var intersections = [
(-b + Math.sqrt(Math.pow(b,2) - 4 * a * c)) / (2 * a),
(-b - Math.sqrt(Math.pow(b,2) - 4 * a * c)) / (2 * a)
];
if (d == 0) {
// only 1 intersection
return [intersections[0]];
}
return intersections;
}
// no intersection
return [];
}
var canvas = document.getElementById("myCanvas");
var xLabel = document.getElementById("x");
var yLabel = document.getElementById("y");
var dxLabel = document.getElementById("dx");
var dyLabel = document.getElementById("dy");
var ctx = canvas.getContext("2d");
var containerR = 200;
canvas.width = containerR * 2;
canvas.height = containerR * 2;
canvas.style["border-radius"] = containerR + "px";
var balls = [
//getBall(canvas.width / 2, canvas.height - 30, 2, -2, 20, "#0095DD"),
//getBall(canvas.width / 3, canvas.height - 50, 3, -3, 30, "#DD9500"),
//getBall(canvas.width / 4, canvas.height - 60, -3, 4, 10, "#00DD95"),
getBall(canvas.width / 2, canvas.height / 5, -2, 26, 40, "#DD0095")
];
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < balls.length; i++) {
var curBall = balls[i];
ctx.beginPath();
ctx.arc(curBall.x, curBall.y, curBall.r, 0, Math.PI * 2);
ctx.fillStyle = curBall.color;
ctx.fill();
ctx.closePath();
// move
curBall.lastX = curBall.x;
curBall.lastY = curBall.y;
if (curBall.xt) { // bounce
curBall.x = curBall.xt;
curBall.xt = false;
} else curBall.x += curBall.dx;
if (curBall.yt) { // bounce
curBall.y = curBall.yt;
curBall.yt = false;
} else curBall.y += curBall.dy;
// bounce
var nextx = curBall.x + curBall.dx,
nexty = curBall.y + curBall.dy;
var ndx = nextx - containerR;
var ndy = nexty - containerR;
var distanceFromCenter = Math.sqrt(ndx * ndx + ndy * ndy);
var rad = containerR - curBall.r;
if (distanceFromCenter >= rad) {
var s = Math.sqrt(curBall.dx * curBall.dx + curBall.dy * curBall.dy);
// calc collision point
// intersetion between line [(x,y)(x+dx,y+dx)]
// and circle [r = rad, c = (R,R)]
// m = rise = y2-y1/x2-x1 = ys/xs
var m1 = curBall.dy / curBall.dx;
// y = mx+n ... n = y-mx
var n1 = nexty - m1 * nextx;
var inters = circleLineInters(rad, containerR, containerR, m1, n1);
// possible intersections 0,1,2
// 0 inters can't hit, do nothing
// 1 inters tangent, only possible outside
if (inters.length == 2) { // line crosses the circle
var hitx = inters[0];
// choose inters x using the trajetory direction
if (curBall.dx < 0) hitx = inters[1];
// calc hity with linear formula y = mx + n
var hity = m1 * hitx + n1;
curBall.xt = hitx;
curBall.yt = hity;
//update speed vectors
var dx = curBall.xt - containerR;
var dy = curBall.yt - containerR;
var df = Math.sqrt(dx * dx + dy * dy);
var normalX = dx / df;
var normalY = dy / df;
var tangentX = -normalY;
var tangentY = normalX;
var normalSpeed = -(normalX * curBall.dx + normalY * curBall.dy);
var tangentSpeed = tangentX * curBall.dx + tangentY * curBall.dy;
curBall.dx = normalSpeed * normalX + tangentSpeed * tangentX;
curBall.dy = normalSpeed * normalY + tangentSpeed * tangentY;
// move cell to reflected position
var ra = Math.atan2(curBall.dy, curBall.dx);
var cdx = hitx - curBall.x;
var cdy = hity - curBall.y;
var collDist = Math.sqrt(cdx * cdx + cdy * cdy);
var rd = s - collDist;
curBall.xt = curBall.xt + rd * Math.cos(ra);
curBall.yt = curBall.yt + rd * Math.sin(ra);
}
}
}
requestAnimationFrame(draw);
}
draw();
canvas {
background: #eee;
border-radius: 50%;
}
<canvas id="myCanvas"></canvas>