4

I need to detect collision circle with any line. I have array with verticles of polygon (x, y) and draw this polygon in loop. For detection I use algorithm, which calculate triangle height. Then I check if this height < 0, then circle collided with line.

The picture, that describe this method:

enter image description here

But I have unexpected result. My circle collide with transparent line (what?). I can't explain how it happens.

Demo at jsfiddle: https://jsfiddle.net/f458rdz6/1/

Function, which check the collisions and response it:

var p = polygonPoints; for (var i = 0, n = p.length; i < n; i++) { var start = i; var end = (i + 1) % n; var x0 = p[start].x; var y0 = p[start].y; var x1 = p[end].x; var y1 = p[end].y; // detection collision var dx = x1 - x0; var dy = y1 - y0; var len = Math.sqrt(dx * dx + dy * dy); var dist = (dx * (this.y - y0) - dy * (this.x - x0)) / len; if (dist < this.radius) { continue; } // calculate reflection, because collided var wallAngle = Math.atan2(dy, dx); var wallNormalX = Math.sin(wallAngle); var wallNormalY = -Math.cos(wallAngle); var d = 2 * (this.velocityX * wallNormalX + this.velocityY * wallNormalY); this.x -= d * wallNormalX; this.y -= d * wallNormalY; } 

var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var polygonPoints = [	{	x: 240, y: 130 }, {	x: 140, y: 100 }, {	x: 180, y: 250 }, {	x: 320, y: 280 }, {	x: 400, y: 50 } ]; var game = {	ball: new Ball() }; function Ball() {	this.x = canvas.width / 2; this.y = canvas.height - 100; this.oldX = this.x - 1; this.oldY = this.y + 1; this.velocityX = 0; this.velocityY = 0; this.radius = 8; }; Ball.prototype.draw = function() {	ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = '#0095DD'; ctx.fill(); ctx.closePath(); }; Ball.prototype.update = function() {	var x = this.x; var y = this.y; this.velocityX = this.x - this.oldX; this.velocityY = this.y - this.oldY; this.x += this.velocityX; this.y += this.velocityY; this.oldX = x; this.oldY = y; }; Ball.prototype.collision = function() {	var p = polygonPoints;	for (var i = 0, n = p.length; i < n; i++) {	var start = i; var end = (i + 1) % n; var x0 = p[start].x; var y0 = p[start].y; var x1 = p[end].x; var y1 = p[end].y; // detection collision var dx = x1 - x0; var dy = y1 - y0; var len = Math.sqrt(dx * dx + dy * dy); var dist = (dx * (this.y - y0) - dy * (this.x - x0)) / len; if (dist < this.radius) {	continue; }	// calculate reflection, because collided var wallAngle = Math.atan2(dy, dx); var wallNormalX = Math.sin(wallAngle); var wallNormalY = -Math.cos(wallAngle); var d = 2 * (this.velocityX * wallNormalX + this.velocityY * wallNormalY); this.x -= d * wallNormalX; this.y -= d * wallNormalY; } }; function drawBall() { ctx.beginPath(); ctx.arc(x, y, ballRadius, 0, Math.PI*2); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); } function drawPolygon() { ctx.beginPath(); ctx.strokeStyle = '#333'; ctx.moveTo(polygonPoints[0].x, polygonPoints[0].y);	for (var i = 1, n = polygonPoints.length; i < n; i++) {	ctx.lineTo(polygonPoints[i].x, polygonPoints[i].y); } ctx.lineTo(polygonPoints[0].x, polygonPoints[0].y); ctx.stroke(); ctx.closePath(); } function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawPolygon(); game.ball.draw(); game.ball.update(); game.ball.collision(); window.requestAnimationFrame(render); } render();
canvas { border: 1px solid #333; }
<canvas id="myCanvas" width="480" height="320"></canvas>

What the problem? Maybe I need use other method for detect collision? I tried to use this one, but if my circle has high speed this method not working.

Thank you.

1 Answer 1

22

Circle line segment intercept

UPDATE

This answer includes line line intercept, moving a line along its normal, distance point (circle) to line, and circle line intercept.

The circle is

var circle = { radius : 500, center : point(1000,1000), } 

The line segment is

var line = { p1 : point(500,500), p2 : point(2000,1000), } 

A point is

var point = { x : 100, y : 100, } 

Thus the function to find the intercept of a line segment width a circle

The function returns an array of up to two point on the line segment. If no points found returns an empty array.

function inteceptCircleLineSeg(circle, line){ var a, b, c, d, u1, u2, ret, retP1, retP2, v1, v2; v1 = {}; v2 = {}; v1.x = line.p2.x - line.p1.x; v1.y = line.p2.y - line.p1.y; v2.x = line.p1.x - circle.center.x; v2.y = line.p1.y - circle.center.y; b = (v1.x * v2.x + v1.y * v2.y); c = 2 * (v1.x * v1.x + v1.y * v1.y); b *= -2; d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - circle.radius * circle.radius)); if(isNaN(d)){ // no intercept return []; } u1 = (b - d) / c; // these represent the unit distance of point one and two on the line u2 = (b + d) / c; retP1 = {}; // return points retP2 = {} ret = []; // return array if(u1 <= 1 && u1 >= 0){ // add point if on the line segment retP1.x = line.p1.x + v1.x * u1; retP1.y = line.p1.y + v1.y * u1; ret[0] = retP1; } if(u2 <= 1 && u2 >= 0){ // second add point if on the line segment retP2.x = line.p1.x + v1.x * u2; retP2.y = line.p1.y + v1.y * u2; ret[ret.length] = retP2; } return ret; } 

UPDATE

Line line intercept.

Returns a point if found else returns undefined.

function interceptLines(line,line1){ var v1, v2, c, u; v1 = {}; v2 = {}; v3 = {}; v1.x = line.p2.x - line.p1.x; // vector of line v1.y = line.p2.y - line.p1.y; v2.x = line1.p2.x - line1.p1.x; //vector of line2 v2.y = line1.p2.y - line1.p1.y; var c = v1.x * v2.y - v1.y * v2.x; // cross of the two vectors if(c !== 0){ v3.x = line.p1.x - line1.p1.x; v3.y = line.p1.y - line1.p1.y; u = (v2.x * v3.y - v2.y * v3.x) / c; // unit distance of intercept point on this line return {x : line.p1.x + v1.x * u, y : line.p1.y + v1.y * u}; } return undefined; } 

Lift Line

Move line along its normal

function liftLine(line,dist){ var v1,l v1 = {}; v1.x = line.p2.x - line.p1.x; // convert line to vector v1.y = line.p2.y - line.p1.y; l = Math.sqrt(v1.x * v1.x + v1.y * v1.y); // get length; v1.x /= l; // Assuming you never pass zero length lines v1.y /= l; v1.x *= dist; // set the length v1.y *= dist; // move the line along its normal the required distance line.p1.x -= v1.y; line.p1.y += v1.x; line.p2.x -= v1.y; line.p2.y += v1.x; return line; // if needed } 

Distance circle (or point) to a line segment

Returns the closest distance to the line segment. It is just the circle center that I am using. So you can replace circle with a point

function circleDistFromLineSeg(circle,line){ var v1, v2, v3, u; v1 = {}; v2 = {}; v3 = {}; v1.x = line.p2.x - line.p1.x; v1.y = line.p2.y - line.p1.y; v2.x = circle.center.x - line.p1.x; v2.y = circle.center.y - line.p1.y; u = (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x); // unit dist of point on line if(u >= 0 && u <= 1){ v3.x = (v1.x * u + line.p1.x) - circle.center.x; v3.y = (v1.y * u + line.p1.y) - circle.center.y; v3.x *= v3.x; v3.y *= v3.y; return Math.sqrt(v3.y + v3.x); // return distance from line } // get distance from end points v3.x = circle.center.x - line.p2.x; v3.y = circle.center.y - line.p2.y; v3.x *= v3.x; // square vectors v3.y *= v3.y; v2.x *= v2.x; v2.y *= v2.y; return Math.min(Math.sqrt(v2.y + v2.x), Math.sqrt(v3.y + v3.x)); // return smaller of two distances as the result } 
Sign up to request clarification or add additional context in comments.

8 Comments

This method works if ball has low speed, but if speed is high, function not working. jsfiddle.net/cmx8arz2 see console, If circle intersect line app should print array of intersection points, but speed of circle high and console log clear.
@rmpstmp Use a line line intercept, using the ball's trajectory as the second line. Move the line along its normal toward the ball by the balls radius. If the lines intersect then use that point to test how far the ball is from the original line segment. if at radius (add EPSILON) from any point on the line, or the endpoints you have hit the ball. Cross product will give what side a point is to a line +right -left and dot product div line leng squared will give the unit dist on the line along its normal to the point. Point and line need to be translated to put line start at zero
@rmpstmp Looking at the fiddle. Just move all the line segments inward by the ball's radius (along the line normals). The use the ball's trajectory as a line and do a line/line intercept. Find the closest intercept to the ball's current location and you have the point of intercept, no need for a circle line segment intercept
Okay, can you describe, what lines I should send to function interceptLines(line,line1)? One of lines should be wall line, but second? I think line from ball's center (b.x, b.y) for example, and what should be second point? Thank you.
You've coded some nice functionality here -- upvote! :-) Using your functions, one can create a line segment that represents the leading edge of the ball from its starting to its ending position. Then calculate the line-line intercept (leading-edge-line versus obstacle-line) to get the initial collision point of the ball versus obstacle. After that, calculate some rebound angles and life is good.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.