Reputation: 2627
Please see this Fiddle: https://jsfiddle.net/sfarbota/wd5aa1wv/2/
I am trying to make the ball bounce inside the circle at the correct angles without losing speed. I think I have the collision detection down, but I am facing 2 issues:
This is partially based off of the answer given here: https://stackoverflow.com/a/12053397/170309 but I had to translate from Java and also skipped a few lines from their example that seemed irrelevant.
Here is the code:
JavaScript:
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; }
Upvotes: 7
Views: 2052
Reputation: 12161
This is a small upgrade to the dot product code by @jcaron. The speed vector reflection he made is perfect but it will change position after the border is crossed and it don't consider the movement during the bounce.
The code below will consider the distance the ball will move each frame before hitting the border and calculated the new position considering the movement before and after the bounce. https://jsfiddle.net/vm3wLk0z/
The difference between @jcaron code and the upgraded one will be more more visible when the ball speed is higher.
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>
Upvotes: 1
Reputation: 17710
My math is rusty, so I'm not quite sure how you could compute the new trajectory of the ball using just a dot product, but I'm sure you can compute it with the relevant trig functions: use atan2
to compute the angle to the collision point and the current trajectory angle, use those two to compute the new angle, and a pair of sin
and cos
multiplied by the speed to get the new x/y speeds.
jsFiddle: https://jsfiddle.net/jacquesc/wd5aa1wv/6/
The important part is:
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);
}
Also note I switched from setInterval
to requestAnimationFrame
, which will make sure there's no more than one update per frame. Ideally you would want to compute the movement based on the actual time elapsed since the last update rather than rely on it being always the same.
Using dot products:
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;
}
Upvotes: 5