I'm creating a library so it's easier for me to create HTML5 canvas games. I'm currently working on the collision detection. This is the code I wrote for line/circle collisions is below. object1
is an object containing the circle's x, y, and radius. object2
is an object containing both points of a line segment.
const point1 = object2.point1;
const point2 = object2.point2;
let newPoint1X = point1.x - object1.x;
let newPoint1Y = point1.y - object1.y;
let newPoint2X = point2.x - object1.x;
let newPoint2Y = point2.y - object1.y;
let lineSlope = (newPoint2Y - newPoint1Y) / (newPoint2X - newPoint1X);
let circleSlope;
if (lineSlope != 0) circleSlope = lineSlope / -1;
else circleSlope = 65535;
let closestX = (newPoint1Y - lineSlope * newPoint1X) / (circleSlope - lineSlope);
let closestY = closestX * circleSlope;
if ((closestX - newPoint1X) * (closestX - newPoint2X) >= 0 && (closestY - newPoint1Y) * (closestY - newPoint2Y) >= 0) {
if ((closestX - newPoint1X) * (closestX - newPoint2X) > 0) {
if (Math.abs(closestX - newPoint1X) > Math.abs(closestX - newPoint2X)) {
closestX = newPoint2X;
closestY = newPoint2Y;
}
else {
closestX = newPoint1X;
closestY = newPoint1Y;
}
}
else {
if (Math.abs(closestY - newPoint1Y) > Math.abs(closestY - newPoint2Y)) {
closestX = newPoint2X;
closestY = newPoint2Y;
}
else {
closestX = newPoint1X;
closestY = newPoint1Y;
}
}
}
return closestX * closestX + closestY * closestY < object1.radius * object1.radius;
Here is an example of object1
and object2
:
let object1 = {
type: "circle",
x: 100,
y: 100,
radius: 50,
color: "#90fcff"
}
let object2 = {
type: "line",
point1: {
x: 30,
y: 20
},
point2: {
x: 360,
y: 310
},
color: "#000000",
lineWidth: 1
}
I tested this code, and it doesn't detect intersections at the right points. Any help with this?
The given answer can be improved.
In the example below the function rayInterceptsCircle
returns true
or false
depending on the intercept of a line segment (ray) and circle by using the distance from the line segment to the circle center.
This is similar to the existing answer however it avoids the need to calculate a expensive square root
The function rayDist2Circle
returns the distance along the line to the point where it intersects the circle, if there is no intercept then the distance is returned as Infinity. It does require up to 2 square roots.
If you have many circles that you must test the line against this function can find the first circle the line intercepts by finding the minimum distance
Use mouse to move line segment endpoint. If line intercepts circle it is rendered in red to the point of intercept.
const ctx = canvas.getContext("2d");
const TAU = Math.PI * 2;
requestAnimationFrame(renderLoop);
var W = canvas.width, H = canvas.height;
const Point = (x, y) => ({x, y});
const Ray = (p1, p2) => ({p1, p2});
const Circle = (p, radius) => ({x: p.x, y: p.y, radius});
function drawRayLeng(ray, len) {
ctx.beginPath();
ctx.lineTo(ray.p1.x, ray.p1.y);
if (len < Infinity) {
const dx = ray.p2.x - ray.p1.x;
const dy = ray.p2.y - ray.p1.y;
const scale = len / Math.hypot(dx, dy);
ctx.lineTo(ray.p1.x + dx * scale , ray.p1.y + dy * scale);
} else {
ctx.lineTo(ray.p2.x, ray.p2.y);
}
ctx.stroke();
}
function drawRay(ray) {
ctx.beginPath();
ctx.lineTo(ray.p1.x, ray.p1.y);
ctx.lineTo(ray.p2.x, ray.p2.y);
ctx.stroke();
}
function drawCircle(circle) {
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, TAU);
ctx.stroke();
}
function rayInterceptsCircle(ray, circle) {
const dx = ray.p2.x - ray.p1.x;
const dy = ray.p2.y - ray.p1.y;
const u = Math.min(1, Math.max(0, ((circle.x - ray.p1.x) * dx + (circle.y - ray.p1.y) * dy) / (dy * dy + dx * dx)));
const nx = ray.p1.x + dx * u - circle.x;
const ny = ray.p1.y + dy * u - circle.y;
return nx * nx + ny * ny < circle.radius * circle.radius;
}
function rayDist2Circle(ray, circle) {
const dx = ray.p2.x - ray.p1.x;
const dy = ray.p2.y - ray.p1.y;
const vcx = ray.p1.x - circle.x;
const vcy = ray.p1.y - circle.y;
var v = (vcx * dx + vcy * dy) * (-2 / Math.hypot(dx, dy));
const dd = v * v - 4 * (vcx * vcx + vcy * vcy - circle.radius * circle.radius);
if (dd <= 0) { return Infinity; }
return (v - Math.sqrt(dd)) / 2;
}
const mouse = {x : 0, y : 0}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
}
document.addEventListener("mousemove", mouseEvents);
const c1 = Circle(Point(150, 120), 60);
const r1 = Ray(Point(0, 50), Point(300, 50));
function renderLoop(time) {
ctx.clearRect(0, 0, W, H);
r1.p1.x = c1.x + Math.cos(time / 5000) * 100;
r1.p1.y = c1.y + Math.sin(time / 5000) * 100;
r1.p2.x = mouse.x;
r1.p2.y = mouse.y;
ctx.lineWidth = 0.5;
drawCircle(c1);
drawRay(r1);
ctx.lineWidth = 5;
if (rayInterceptsCircle(r1, c1)) {
ctx.strokeStyle = "red";
drawRayLeng(r1, rayDist2Circle(r1, c1));
} else {
drawRay(r1);
}
ctx.strokeStyle = "black";
requestAnimationFrame(renderLoop);
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas" width="300" height="250"></canvas>