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; }
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;
}