javascriptcanvashtml5-canvas

Detection and response ball-to-wall collision inside any polygon


Need to code good method for detection and response ball-to-wall collision inside any polygon.

For example, I have a method which draws a ball which flys inside a rectangle.

ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();

Detect and response that collision very simple.

if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
    dx = -dx;
}

if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) {
    dy = -dy;
}

But I have a polygon: variable with positions (x and y) of each point.

var polygonPoints = [
{
    x: 240,
    y: 30
},
{
    x: 140,
    y: 100
},
{
    x: 180,
    y: 250
},
{
    x: 320,
    y: 280
},
{
    x: 400,
    y: 50
}

];

And function which draws my polygon:

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();

How I can detect and response collisions inside a polygon?

Demo on jsfiddle.

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var ballRadius = 10;
var x = canvas.width/2;
var y = canvas.height-30;
var dx = 2;
var dy = -2;
var polygonPoints = [
    {
        x: 240,
        y: 30
    },
    {
        x: 140,
        y: 100
    },
    {
        x: 180,
        y: 250
    },
    {
        x: 320,
        y: 280
    },
    {
        x: 400,
        y: 50
    }
];

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 draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawBall();
    drawPolygon();
    
    if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
        dx = -dx;
    }
    if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) {
        dy = -dy;
    }
    
    x += dx;
    y += dy;
    window.requestAnimationFrame(draw);
}

draw();
canvas {
    border: 1px solid #333;
}
<canvas id="myCanvas" width="480" height="320"></canvas>


Solution

  • Here's how to test for circle (ball) collisions) versus any line in your polygon.

    First, calculate the closes point on a line relative to your ball:

    function calcClosestPtOnSegment(x0,y0,x1,y1,cx,cy){
    
        // calc delta distance: source point to line start
        var dx=cx-x0;
        var dy=cy-y0;
    
        // calc delta distance: line start to end
        var dxx=x1-x0;
        var dyy=y1-y0;
    
        // Calc position on line normalized between 0.00 & 1.00
        // == dot product divided by delta line distances squared
        var t=(dx*dxx+dy*dyy)/(dxx*dxx+dyy*dyy);
    
        // calc nearest pt on line
        var x=x0+dxx*t;
        var y=y0+dyy*t;
    
        // clamp results to being on the segment
        if(t<0){x=x0;y=y0;}
        if(t>1){x=x1;y=y1;}
    
        return({ x:x, y:y, isOnSegment:(t>=0 && t<=1) });
    }
    

    Second, test if the ball is close enough to collide with that line like this:

    var dx=ballX-nearestX;
    var dy=ballY-nearestY
    var isColliding=(dx*dx+dy*dy<ballRadius*ballRadius);
    

    Finally, if the ball collided with that side, calculate the ball's reflection angle (== its outgoing angle):

    Here's an illustration of the angles involved in the calculation:

    enter image description here

    And here's some pseudo-code showing how to do the calculation:

    var wallNormalAngle = wallAngle-PI/2; // assuming clockwise angle calculations
    var differenceAngle = incidenceAngle - wallNormalAngle;
    var reflectionAngle = incidenceAngle + 2 * differenceAngle