5

Im using Newtons equations to make the balls in this program that I'm currently working on to be "split" away when they collide with eachother, but sometimes they get stuck into eachother and that causes lot's of trouble.

.

This is my code:

<center>
<canvas id="canvas" style="border: 2px solid black; cursor: crosshair;" width="1000"                 height="500"></canvas>
</center>

<script>
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")

var w = canvas.width
var h = canvas.height

var ball = []

var gravity = 0.3
var force = 0.2

var mouse = {
d: false,
x1: 0,
y1: 0,
x2: 0,
y2: 0,
}




window.onmousedown = function(e) {
mouse.d = true
mouse.x1 = mouse.x2 = e.pageX - canvas.getBoundingClientRect().left
mouse.y1 = mouse.y2 = e.pageY - canvas.getBoundingClientRect().top
}
window.onmousemove = function(e) {
if (mouse.d) {
    mouse.x2 = e.pageX - canvas.getBoundingClientRect().left
    mouse.y2 = e.pageY - canvas.getBoundingClientRect().top
} else {
    mouse.x1 = mouse.x2 = e.pageX - canvas.getBoundingClientRect().left
    mouse.y1 = mouse.y2 = e.pageY - canvas.getBoundingClientRect().top
}
}
window.onmouseup = function() {
if (mouse.d) {
    mouse.d = false

    var dx = (mouse.x1 - mouse.x2);
    var dy = (mouse.y1 - mouse.y2);
    var mag = Math.sqrt(dx * dx + dy * dy);

    ball.push({
        x: mouse.x1,
        y: mouse.y1,
        r: Math.floor(Math.random() * 20) + 10,
        vx: dx / mag * -(mag * force),
        vy: dy / mag * -(mag * force),
        b: 0.7,
    })
}
}
document.onselectstart = function() {return false}
document.oncontextmenu = function() {return false}


setInterval(update, 1000 / 60)
function update() {
ctx.clearRect(0, 0, w, h)

ctx.beginPath()
ctx.moveTo(mouse.x1, mouse.y1)
ctx.lineTo(mouse.x2, mouse.y2)
ctx.stroke()
ctx.closePath()

for (i = 0; i < ball.length; i++) {
    ball[i].vy += gravity
    ball[i].x += ball[i].vx
    ball[i].y += ball[i].vy

    if (ball[i].x > w - ball[i].r) {
        ball[i].x = w - ball[i].r
        ball[i].vx *= -ball[i].b
    }
    if (ball[i].x < ball[i].r) {
        ball[i].x = ball[i].r
        ball[i].vx *= -ball[i].b
    }
    if (ball[i].y > h - ball[i].r) {
        ball[i].y = h - ball[i].r
        ball[i].vy *= -ball[i].b
    }
    if (ball[i].y < ball[i].r) {
        ball[i].y = ball[i].r
        ball[i].vy *= -ball[i].b
    }

    for (j = i + 1; j < ball.length; j++) {
        var dx = ball[i].x - ball[j].x
        var dy = ball[i].y - ball[j].y
        var dist = Math.sqrt(dx * dx + dy * dy)
        if (Math.abs(dx) + Math.abs(dy) != 0 && dist <= ball[i].r + ball[j].r) {
            var angle = Math.atan2(dy, dx)

            var sp1 = Math.sqrt(ball[i].vx*ball[i].vx + ball[i].vy*ball[i].vy);
            var sp2 = Math.sqrt(ball[j].vx*ball[j].vx + ball[j].vy*ball[j].vy);

            var dir1 = Math.atan2(ball[i].vy, ball[i].vx);
            var dir2 = Math.atan2(ball[j].vy, ball[j].vx);

            var vx1 = sp1 * Math.cos(dir1 - angle);
            var vy1 = sp1 * Math.sin(dir1 - angle);
            var vx2 = sp2 * Math.cos(dir2 - angle);
            var vy2 = sp2 * Math.sin(dir2 - angle);

            var fvx1 = ((ball[i].r - ball[j].r) * vx1 + (2 * ball[j].r) * vx2) / (ball[i].r + ball[j].r);
            var fvx2 = ((2 * ball[i].r) * vx1 + (ball[j].r - ball[i].r) * vx2) / (ball[i].r + ball[j].r);
            var fvy1 = vy1;
            var fvy2 = vy2;

            ball[i].vx = Math.cos(angle) * fvx1 + Math.cos(angle + Math.PI/2) * fvy1;
            ball[i].vy = Math.sin(angle) * fvx1 + Math.sin(angle + Math.PI/2) * fvy1;
            ball[j].vx = Math.cos(angle) * fvx2 + Math.cos(angle + Math.PI/2) * fvy2;
            ball[j].vy = Math.sin(angle) * fvx2 + Math.sin(angle + Math.PI/2) * fvy2;
        }
    }


    ctx.beginPath()
    ctx.arc(ball[i].x, ball[i].y, ball[i].r, 0, Math.PI * 2, false)
    ctx.fillStyle = "black"
    ctx.fill()
    ctx.closePath()
}
}
</script>

And when you have lot's of balls spawned and their speed is fast this happens:

Why? Anyone know how I can fix this?

  • http://jsfiddle.net/bxy3p/ –  Jun 16 '13 at 10:31
  • I know I'm not willing to read this to figure out what your problem is. I don't know who will be. – duffymo Jun 16 '13 at 10:33
  • I already know what your problem is... – Mich' Jun 16 '13 at 16:24
  • Perhaps this will help: http://stackoverflow.com/questions/12952752/javascript-collision-detection-between-drawable-circles?rq=1 –  Jun 16 '13 at 20:50
  • A little bit only, but thanks! –  Jun 24 '13 at 06:40
  • Why are you using angles / sin / cos / atan2 ? Try to rewrite your code using vectors only. BTW. put "var b = ball[i];" into the first for loop and use it. – Ivan Kuckir Jun 25 '13 at 19:05
  • What happens with only two balls at high speed? With many balls, do they ever get stuck out when they're *not* touching a wall? – Beta Jun 25 '13 at 22:17
  • @Ivan Kuckir - How can I rewrite the code with Vectors, I don't even know how a Vector works. –  Aug 06 '13 at 19:27
  • @Beta - Yes they do get stuck without touching a wall. –  Aug 06 '13 at 19:28
  • Vector spaces are mathematical structures, studied by linear algebra. They may save you a lot of time and let you create more clean and efficient code. You should read some book or Wikipedia. – Ivan Kuckir Aug 07 '13 at 19:48
  • Nah, must I read a whole book to get an answer on this question? –  Aug 08 '13 at 07:02

3 Answers3

2

Your solution seem to have penalty force based on the velocity only. This allows spheres to penetrate, and when there's no velocity in any of the spheres, none of them will try to fix the penetration. To resolve this problem, you need to add a position based penalty. A very simple solution is to use springs. Compute the penetration length and push the intersecting spheres from each other using Hooke's law.

The best solution would be to use an implicit contact solving algorithm. That allows for more stiff contacts, but the algorithms for that are way more complicated. I'd suggest you to use a 2D physics engine for quick and good results: the JavaScript ports of Box2D seem to be most used.

Community
  • 1
  • 1
schteppe
  • 1,984
  • 13
  • 15
  • Yes I know about that one, Box2D, but I still want an explainasion and a real equation to calculate the problem instead of using an existing library and not knowing how my code really works. –  Jun 27 '13 at 12:23
  • Of course. Here's a site with some good reading. It's from a course I took. http://www8.cs.umu.se/kurser/5DV058/HT12/5dv058sched.html – schteppe Jun 27 '13 at 12:41
  • Nah, must I go a whole course? Can't you explain, you did the cource, right? –  Jun 27 '13 at 18:26
  • Not sure how to fit everything in an answer on here, sorry. However, I found another answer that may give you some hints: http://stackoverflow.com/questions/345838/ball-to-ball-collision-detection-and-handling – schteppe Jul 08 '13 at 12:08
0

I am very much with @schteppe's answer above. Just wanted to tell you about this link here : http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/

Check out the "Why should I use it?" section. It might prove to have some hinting to the answer to your question that what is exactly wrong with your code.

I also have been trying to make your code work, by the way, but to no substantial result. You can check my fiddle(which I copied from yours) here : http://jsfiddle.net/sukhmeetsd/joqpqp49/

var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")

var w = canvas.width
var h = canvas.height

var d = 5; //distance to move on collision

var ball = []

var gravity = 0.3
var force = 0.2

var mouse = {
    d: false,
    x1: 0,
    y1: 0,
    x2: 0,
    y2: 0,
}




window.onmousedown = function (e) {
    mouse.d = true
    mouse.x1 = mouse.x2 = e.pageX - canvas.getBoundingClientRect().left
    mouse.y1 = mouse.y2 = e.pageY - canvas.getBoundingClientRect().top
}
window.onmousemove = function (e) {
    if (mouse.d) {
        mouse.x2 = e.pageX - canvas.getBoundingClientRect().left
        mouse.y2 = e.pageY - canvas.getBoundingClientRect().top
    } else {
        mouse.x1 = mouse.x2 = e.pageX - canvas.getBoundingClientRect().left
        mouse.y1 = mouse.y2 = e.pageY - canvas.getBoundingClientRect().top
    }
}
window.onmouseup = function () {
    if (mouse.d) {
        mouse.d = false

        var dx = (mouse.x1 - mouse.x2);
        var dy = (mouse.y1 - mouse.y2);
        var mag = Math.sqrt(dx * dx + dy * dy);

        ball.push({
            x: mouse.x1,
            y: mouse.y1,
            r: Math.floor(Math.random() * 20) + 10,
            vx: dx / mag * -(mag * force),
            vy: dy / mag * -(mag * force),
            b: 0.7,
        })
    }
}

function getRandomColor() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

document.onselectstart = function () {
    return false
}
document.oncontextmenu = function () {
    return false
}


setInterval(update, 1000/60)

function update() {
    ctx.clearRect(0, 0, w, h)

    ctx.beginPath()
    ctx.moveTo(mouse.x1, mouse.y1)
    ctx.lineTo(mouse.x2, mouse.y2)
    ctx.stroke()
    ctx.closePath()

    for (i = 0; i < ball.length; i++) {
        ball[i].vy += gravity
        ball[i].x += ball[i].vx
        ball[i].y += ball[i].vy

        if (ball[i].x > w - ball[i].r) {
            ball[i].x = w - ball[i].r
            ball[i].vx *= -ball[i].b
        }
        if (ball[i].x < ball[i].r) {
            ball[i].x = ball[i].r
            ball[i].vx *= -ball[i].b
        }
        if (ball[i].y > h - ball[i].r) {
            ball[i].y = h - ball[i].r
            ball[i].vy *= -ball[i].b
        }
        if (ball[i].y < ball[i].r) {
            ball[i].y = ball[i].r
            ball[i].vy *= -ball[i].b
        }

        for (j = i + 1; j < ball.length; j++) {
            var dx = ball[i].x - ball[j].x
            var dy = ball[i].y - ball[j].y
            var dist = Math.sqrt(dx * dx + dy * dy)
            if (Math.abs(dx) + Math.abs(dy) != 0 && dist <= ball[i].r + ball[j].r) {

                var angle = Math.atan2(dy, dx)

                var sp1 = Math.sqrt(ball[i].vx * ball[i].vx + ball[i].vy * ball[i].vy);
                var sp2 = Math.sqrt(ball[j].vx * ball[j].vx + ball[j].vy * ball[j].vy);

                var dir1 = Math.atan2(ball[i].vy, ball[i].vx);
                var dir2 = Math.atan2(ball[j].vy, ball[j].vx);

                d = Math.ceil(ball[i].r+ball[j].r-dist)/2;

                //moving them back
                ball[i].x = ball[i].x - Math.cos(dir1)*d-1;
                ball[i].y = ball[i].y - Math.sin(dir1)*d-1;
                ball[j].x = ball[j].x + Math.cos(dir2)*d+1;
                ball[j].y = ball[j].y + Math.sin(dir2)*d+1;

                //Checking for distance again
                /*dx = ball[i].x - ball[j].x;
                dy = ball[i].y - ball[j].y;
                dist = Math.sqrt(dx * dx + dy * dy);
                if (Math.abs(dx) + Math.abs(dy) != 0 && dist <= ball[i].r + ball[j].r){
                      ball[i].x = ball[i].x + Math.cos(dir1)*2*d;
                        ball[i].y = ball[i].y + Math.sin(dir1)*d*2;
                        ball[j].x = ball[j].x - Math.cos(dir2)*d*2;
                      ball[j].y = ball[j].y - Math.sin(dir2)*d*2;
                }*/

                var vx1 = sp1 * Math.cos(dir1 - angle);
                var vy1 = sp1 * Math.sin(dir1 - angle);
                var vx2 = sp2 * Math.cos(dir2 - angle);
                var vy2 = sp2 * Math.sin(dir2 - angle);

                var fvx1 = ((ball[i].r - ball[j].r) * vx1 + (2 * ball[j].r) * vx2) / (ball[i].r + ball[j].r);
                var fvx2 = ((2 * ball[i].r) * vx1 + (ball[j].r - ball[i].r) * vx2) / (ball[i].r + ball[j].r);
                var fvy1 = vy1;
                var fvy2 = vy2;

                ball[i].vx = Math.cos(angle) * fvx1 + Math.cos(angle + Math.PI / 2) * fvy1;
                ball[i].vy = Math.sin(angle) * fvx1 + Math.sin(angle + Math.PI / 2) * fvy1;
                ball[j].vx = Math.cos(angle) * fvx2 + Math.cos(angle + Math.PI / 2) * fvy2;
                ball[j].vy = Math.sin(angle) * fvx2 + Math.sin(angle + Math.PI / 2) * fvy2;
            }
        }


        ctx.beginPath()
        ctx.arc(ball[i].x, ball[i].y, ball[i].r, 0, Math.PI * 2, false)
        ctx.fillStyle = getRandomColor();
        ctx.fill();
        ctx.closePath();
    }
}

My code doesn't let the balls stick but they are in a constant state of agitation. I was about to implement the Hooke's Law as suggested by @schteppe but then I heard about Box2d and its magic.

Sukhmeet Singh
  • 29
  • 1
  • 11
0

Just use overlap. It can be can be calculated like difference between sum of radiuses of 2 particles and distance between them. Code below written in javascript. And it works pretty good in my simulation:

let overlap = particle1.r + particle2.r - distance(particle1, particle2);
particle1.x += 0.5*overlap;
particle2.x -= 0.5*overlap;