Summary
This question is in JavaScript, but an answer in any language, pseudo-code, or just the maths would be great!
I have been trying to implement the Separating-Axis-Theorem to accomplish the following:
- Detecting an intersection between a convex polygon and a circle.
- Finding out a translation that can be applied to the circle to resolve the intersection, so that the circle is barely touching the polygon but no longer inside.
- Determining the axis of the collision (details at the end of the question).
I have successfully completed the first bullet point and you can see my javascript code at the end of the question. I am having difficulties with the other parts.
Resolving the intersection
There are plenty of examples online on how to resolve the intersection in the direction with the smallest/shortest overlap of the circle. You can see in my code at the end that I already have this calculated.
However this does not suit my needs. I must resolve the collision in the opposite direction of the circle's trajectory (assume I already have the circle's trajectory and would like to pass it into my function as a unit-vector or angle, whichever suits).
You can see the difference between the shortest resolution and the intended resolution in the below image:
How can I calculate the minimum translation vector for resolving the intersection inside my test_CIRCLE_POLY
function, but that is to be applied in a specific direction, the opposite of the circle's trajectory?
My ideas/attempts:
- My first idea was to add an additional axis to the axes that must be tested in the SAT algorithm, and this axis would be perpendicular to the circle's trajectory. I would then resolve based on the overlap when projecting onto this axis. This would sort of work, but would resolve way to far in most situations. It won't result in the minimum translation. So this won't be satisfactory.
- My second idea was to continue to use magnitude of the shortest overlap, but change the direction to be the opposite of the circle's trajectory. This looks promising, but there are probably many edge-cases that I haven't accounted for. Maybe this is a nice place to start.
Determining side/axis of collision
I've figured out a way to determine which sides of the polygon the circle is colliding with. For each tested axis of the polygon, I would simply check for overlap. If there is overlap, that side is colliding.
This solution will not be acceptable once again, as I would like to figure out only one side depending on the circle's trajectory.
My intended solution would tell me, in the example image below, that axis A is the axis of collision, and not axis B. This is because once the intersection is resolved, axis A is the axis corresponding to the side of the polygon that is just barely touching the circle.
My ideas/attempts:
Currently I assume the axis of collision is that perpendicular to the MTV (minimum translation vector). This is currently incorrect, but should be the correct axis once I've updated the intersection resolution process in the first half of the question. So that part should be tackled first.
Alternatively I've considered creating a line from the circle's previous position and their current position + radius, and checking which sides intersect with this line. However, there's still ambiguity, because on occasion there will be more than one side intersecting with the line.
My code so far
function test_CIRCLE_POLY(circle, poly, circleTrajectory) {
// circleTrajectory is currently not being used
let axesToTest = [];
let shortestOverlap = +Infinity;
let shortestOverlapAxis;
// Figure out polygon axes that must be checked
for (let i = 0; i < poly.vertices.length; i++) {
let vertex1 = poly.vertices[i];
let vertex2 = poly.vertices[i + 1] || poly.vertices[0]; // neighbouring vertex
let axis = vertex1.sub(vertex2).perp_norm();
axesToTest.push(axis);
}
// Figure out circle axis that must be checked
let closestVertex;
let closestVertexDistSqr = +Infinity;
for (let vertex of poly.vertices) {
let distSqr = circle.center.sub(vertex).magSqr();
if (distSqr < closestVertexDistSqr) {
closestVertexDistSqr = distSqr;
closestVertex = vertex;
}
}
let axis = closestVertex.sub(circle.center).norm();
axesToTest.push(axis);
// Test for overlap
for (let axis of axesToTest) {
let circleProj = proj_CIRCLE(circle, axis);
let polyProj = proj_POLY(poly, axis);
let overlap = getLineOverlap(circleProj.min, circleProj.max, polyProj.min, polyProj.max);
if (overlap === 0) {
// guaranteed no intersection
return { intersecting: false };
}
if (Math.abs(overlap) < Math.abs(shortestOverlap)) {
shortestOverlap = overlap;
shortestOverlapAxis = axis;
}
}
return {
intersecting: true,
resolutionVector: shortestOverlapAxis.mul(-shortestOverlap),
// this resolution vector is not satisfactory, I need the shortest resolution with a given direction, which would be an angle passed into this function from the trajectory of the circle
collisionAxis: shortestOverlapAxis.perp(),
// this axis is incorrect, I need the axis to be based on the trajectory of the circle which I would pass into this function as an angle
};
}
function proj_POLY(poly, axis) {
let min = +Infinity;
let max = -Infinity;
for (let vertex of poly.vertices) {
let proj = vertex.projNorm_mag(axis);
min = Math.min(proj, min);
max = Math.max(proj, max);
}
return { min, max };
}
function proj_CIRCLE(circle, axis) {
let proj = circle.center.projNorm_mag(axis);
let min = proj - circle.radius;
let max = proj + circle.radius;
return { min, max };
}
// Check for overlap of two 1 dimensional lines
function getLineOverlap(min1, max1, min2, max2) {
let min = Math.max(min1, min2);
let max = Math.min(max1, max2);
// if negative, no overlap
let result = Math.max(max - min, 0);
// add positive/negative sign depending on direction of overlap
return result * ((min1 < min2) ? 1 : -1);
};