No need to use dot() or reflect(). Use the conservation of energy to calculate the new velocity of the ball after collision with a stationary object.

A ball hitting a circle acts as if it hit a line tangent to the point on the circle. We actually use the normal vector to this tangent line (the collision normal), so there's no need to even calculate the tangent line!
The collision normal is simply the normalized vector that starts at the center of the circle and extends to the point of collision (on the circle.)
Given the collision normal n, we can calculate the new velocity of the ball using this pseudo-code:
function resolveFixedCollision(obj, n)
set c to componentVector(obj.velocity, n)
set obj.velocity to v-2*c
end function
And explained in words:
...split the vector v into two components, one in the direction n(the
normal component) and the other in the direction of the wall (the
tangential component). Because there are no forces acting on the ball
in the tangential direction, its velocity in that direction remains
unchanged. In the other direction, the velocity is simply reversed.
This way, the energy of the ball, the only moving object in the
collision, remains unchanged. The end result of this is that you can
find the new velocity by simply subtracting twice the normal component.
Source: Mathematics and Physics for Programmers, p. 200-201
Finally, I have implemented the above algorithm using SAT vector functions, as requested. Click "Run code snippet" to see how the bullet moves. The meat of the logic is inside reflectBullet(). I also modified the representations of a_beam_of_light
and a_curved_mirror
:
- velocity and position changed to SAT.Vectors with x and y components
- moved the initial position of the bullet so its path before collision is more visible
- Used SAT.Circle instead of creating new SAT.Circles each time testCircleCollision() is called
var reflectBullet = function(bullet, wall, sat_response) {
function resolveFixedCollision(obj, n) {
// Set c to componentVector(obj.velocity, n).
var c = new SAT.Vector();
c.copy(obj.v);
c.project(n);
//set obj.velocity to v-2*c
c.scale(2, 2);
obj.v.sub(c);
}
// The collision normal
var n = new SAT.Vector(wall.c.pos.x - bullet.c.pos.x,
wall.c.pos.y - bullet.c.pos.y).normalize();
// Calculate new velocity of bullet after collision
resolveFixedCollision(bullet, n);
// Move bullet to point of collision (if it went through wall)
bullet.c.pos.sub(sat_response.response.overlapV);
}
var testCircleCollision = function(a, b) {
var response = new SAT.Response();
var collided = SAT.testCircleCircle(a.c, b.c, response);
return {
collided: collided,
response: response
};
};
var a_beam_of_light = {
c: new SAT.Circle(new SAT.Vector(25 - 15, 40), 1),
// x: 25,
// y: 40,
// r: 1,
v: new SAT.Vector(1, 0)
// v: 1,
// origX: 0,
// origY: 40,
// targetX: 50,
// targetY: 40,
}
var a_curved_mirror = {
c: new SAT.Circle(new SAT.Vector(50, 50), 25)
// x: 50,
// y: 50,
// r: 25
}
var step_world = function() {
// step the world forward, moving the bullet towards it's target
a_beam_of_light.c.pos = a_beam_of_light.c.pos.add(a_beam_of_light.v)
// a_beam_of_light.x = a_beam_of_light.x + a_beam_of_light.v;
// console.log(a_beam_of_light, a_curved_mirror)
var test_results = testCircleCollision(a_beam_of_light, a_curved_mirror);
if (test_results.collided) {
reflectBullet(a_beam_of_light, a_curved_mirror, test_results);
}
}
var el = document.getElementById('c');
var ctx = el.getContext('2d');
// draw a SAT.Circle
function renderCircle(c) {
ctx.beginPath();
ctx.arc(c.pos.x, c.pos.y, c.r, 0, 2 * Math.PI);
ctx.stroke();
}
renderCircle(a_curved_mirror.c);
renderCircle(a_beam_of_light.c);
for (var x = 0; x < 40; x++) {
step_world();
if (x % 5 == 0) {
renderCircle(a_beam_of_light.c);
}
}
canvas {
border: 1px solid #ccc
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/sat/0.6.0/SAT.min.js"></script>
<canvas id="c" width="500" height="150"></canvas>