Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javascript collision detection between drawable circles


First of all please don't be too critical with my English. I'm not a native speaker. I hope I can explain myself never the less! Further more, I read that I should show I made some efforts to solve the given problem myself. To show this my post became relatively long.

What I want to do:

I am new (three weeks) to javascript and I try to build a pool billiard table via html5 canvas and javascript. In the end a user should be able to present a certain playing situation on the virtual pool tabe by dragging the sixteen balls around. I found a lot of hints here on stackoverflow concerning paths and drag & drop.

What already works:

By the while the circles almost look like pool balls and I'm able to move them around with the mouse. So far so good.

What the problem is:

Certainly pool balls collide in reality and don't overlap. Concerning the collision detection I found a helpful post which told me to do this using the Pythagorean therm a*a + b*b = c*c. I understand this concept. What I definitely don't understand is where I have to implement the detection within my code. In my script there is a function (moveBall00(e)) which calculates the new x- and y-coordinates if the balls are dragged around (dragok = true). But how can a calculation of the distance between the balls be made while the new coordinates of the dragged ball are calculated at the same time? This could easily be newbie-question!

I tried this:

  1. new variable “x”

    var x = true;

  2. new function to detect the distance between the balls

    function collision() { if((Math.pow(bp[1][0] - bp[1][1], 2)) + (Math.pow(bp[2][0] - bp[2][1], 2)) <= 576 ) { x = false; } }

  3. call “collision()” every 10 millisecounds

    (function isIntersecting() { return setInterval(collision, 10); })();

  4. only drag the ball if “x=true”

    function moveBall00(e) { if (dragok && x) { bp[1][0] = e.pageX - canvas.offsetLeft; bp[2][0] = e.pageY - canvas.offsetTop; } }

The result is that the circles (balls) overlaped until they came to a rest. They overlaped the more the quicker I dragged the ball. This seams to be a first-after-problem?!

For experienced developers it is most certain a walk through the park but not for me.

I'm the insistent type and will learn to do this stuff in javascript but at the moment I have no clue on how to solve the mentioned problem.

I would appreciate any help with this an will post the future result here!

Here is what I did so far:

(To simplify it only the white ball is drawable.)

<!doctype html>

<html>

    <head>
    <meta charset="UTF-8" />
            <title>Collision detection</title>
    </head>

    <body>

            <canvas id="pooltable" width="1200" height="660">This text is displayed if your browser does not support HTML5 Canvas</canvas>

        <script>

            var canvas;
            var ctx;
            var width = 1200;
            var height = 660;
            var dragok = false;

            var bp = new Array();
                // color of balls "ballColor"
                bp[0] = new Array("rgba(255,255,255,1)","rgba(231,214,8,1)");
                // x-position of balls "xBallPosition"
                bp[1] = new Array(20,50);
                // y-position of balls "yBallPosition"
                bp[2] = new Array(50,50);
                // color of stripes "stripe"
                bp[3] = new Array("rgba(255,255,255,0)","rgba(231,214,8,1)");
                // color of stripe strokes "stripeStroke"
                bp[4] = new Array("rgba(255,255,255,0)","rgba(231,214,8,1)");
                // ball numbers "ballNumber"
                bp[5] = new Array(" ","1");
                // position of ball numbers "positionBallNumber"
                bp[6] = new Array();

            function init() {
                canvas = document.getElementById("pooltable");
                ctx = canvas.getContext("2d");
                return setInterval(draw, 1);
            }

            function clear() {
                ctx.clearRect(0, 0, width, height);
            }

            function rect(x,y,w,h) {
                ctx.beginPath();
                ctx.rect(x,y,w,h);
                ctx.closePath();
                ctx.fill();
            }

            function ball(ballColor, xBallPosition, yBallPosition, ballRadius, angle, stripe, stripeStroke, circleRadius, ballNumber, positionBallNumber) {

                ctx.fillStyle = ballColor;
                ctx.shadowBlur = 5;
                ctx.shadowColor = "rgba(0,0,0,1)";
                ctx.beginPath();
                ctx.arc(xBallPosition, yBallPosition, ballRadius, angle, Math.PI * 2, true);
                ctx.closePath();
                ctx.fill();

                ctx.fillStyle = stripe;
                ctx.strokeStyle = stripeStroke;
                ctx.shadowColor = "rgba(0,0,0,0)";
                ctx.beginPath();
                ctx.moveTo(xBallPosition - 7, yBallPosition - 8);
                ctx.bezierCurveTo(xBallPosition - 8, yBallPosition - 13, xBallPosition + 8, yBallPosition - 13, xBallPosition + 7, yBallPosition - 8);
                ctx.lineTo(xBallPosition + 7, yBallPosition + 8);
                ctx.bezierCurveTo(xBallPosition + 8, yBallPosition + 13, xBallPosition - 8, yBallPosition + 13, xBallPosition - 7, yBallPosition + 8);
                ctx.closePath();
                ctx.stroke();
                ctx.fill();

                ctx.fillStyle =  "rgba(255,255,255,1)";
                ctx.shadowColor = "rgba(0,0,0,0)";
                ctx.beginPath();
                ctx.arc(xBallPosition, yBallPosition, circleRadius, angle, Math.PI * 2, true);
                ctx.closePath();
                ctx.fill();

                ctx.fillStyle = "rgba(0,0,0,1)";
                ctx.font = "normal normal lighter 7px Helvetica";
                ctx.textBaseline = "middle";
                ctx.fillText(ballNumber, xBallPosition - positionBallNumber, yBallPosition + 1);

                var gradient = ctx.createRadialGradient(xBallPosition, yBallPosition, 1, xBallPosition + 3, yBallPosition + 3, 12);
                gradient.addColorStop(0, "rgba(255,255,255,0.6)");
                gradient.addColorStop(1, "rgba(0,0,0,0.2)");
                ctx.fillStyle = gradient;
                ctx.strokeStyle = "rgba(0,0,0,0.4)";
                ctx.shadowColor = "rgba(0,0,0,0)";
                ctx.beginPath();
                ctx.arc(xBallPosition, yBallPosition, ballRadius, angle, Math.PI * 2, true);
                ctx.closePath();
                ctx.stroke();
                ctx.fill();

            }

            function draw() {

                clear();

                table = new rect(0,0,width,height);

                ball00 = new ball(bp[0][0], bp[1][0], bp[2][0], 12, 0, bp[3][0], bp[4][0], 6, bp[5][0], 0);
                ball01 = new ball(bp[0][1], bp[1][1], bp[2][1], 12, 0, bp[3][0], bp[4][1], 6, bp[5][1], 2);

            }

            function myDown(e) {
                if (e.pageX < bp[1][0] + 6 + canvas.offsetLeft && e.pageX > bp[1][0] - 6 + canvas.offsetLeft && e.pageY < bp[2][0] + 6 + canvas.offsetTop && e.pageY > bp[2][0] - 6 + canvas.offsetTop) {
                    bp[1][0] = e.pageX - canvas.offsetLeft;
                    bp[2][0] = e.pageY - canvas.offsetTop;
                    dragok = true;
                    canvas.onmousemove = moveBall00;
                }
                else {
                    dragok = false;
                }
            }

            function myUp() {
                dragok = false;
                canvas.onmousemove = null;
            }

            function moveBall00(e) {
                if (dragok) {
                    bp[1][0] = e.pageX - canvas.offsetLeft;
                    bp[2][0] = e.pageY - canvas.offsetTop;
                }
            }

            init();

            canvas.onmousedown = myDown;
            canvas.onmouseup = myUp;

        </script>

    </body>

</html>
like image 210
Thomas Kröhnert Avatar asked Oct 18 '12 10:10

Thomas Kröhnert


1 Answers

The problem you are facing is totally unrelated to the underlying rendering techinique. This is just a math problem.

What you basically want to do for your situation is calculate the distance between the ball you drag and any ball on the table. If the distance is too short a collision happened.

For two objects this is simple, you need three values: The x and y coordinates of the balls and their radii.

var ball = {
    x: 100,
    y: 100
    r: 10
};

To calculate the distance you do this:

var squareX = Math.pow(Math.abs(ballA.x - ballB.x), 2);
var squareY = Math.pow(Math.abs(ballA.y - ballB.y), 2);
var hypothenuse = Math.sqrt(squareX + squareY);
var distance = hypothenuse - ballA.r - ballB.r;

if (distance >= 0) {
    // new position is valid
}

Since you have a number of balls on a pool table you need to call this code once for each ball on the table that is not moved to compare all of them.

function canMove(ballA, ballB) {
    var squareX = Math.pow(Math.abs(ballA.x - ballB.x), 2);
    var squareY = Math.pow(Math.abs(ballA.y - ballB.y), 2);
    var hypothenuse = Math.sqrt(squareX + squareY);
    var distance = hypothenuse - ballA.r - ballB.r;

    if (distance >= 0) {
        return true;
    }
    return false;
}


function canDrag(ball, balls) {
    var isMovable = true;
    for (var i = balls.length-1; i >= 0; i--) {
        isMovable = canMove(ball, balls[i]);
        if (!isMovable) {
            return false;
        }
    }
    return true;
}

In the last snippet I assumed that your ball objects are stored in the balls array. I hope this helps.

like image 76
Torsten Walter Avatar answered Oct 11 '22 19:10

Torsten Walter