### Collision detection in SVG

Using Snap.svg to detect proximity and collisions and change object trajectory.

06/05/2014

I was recently looking in to Adobe's snap.svg in preparation for a heavily svg focused web project, and trying to rig up a simple prototype where a number of balls move around the screen, changing trajectory when they collide and and drawing lines between each other when they pass a certain proximity. I was using snaps' animate functionality, but with so many animation loops running currently, the prototype chugged, which is when I realized I was going about it all wrong. Someone on Stack Overflow suggested just running a single loop, and not having it run in such rapid succession, so I rewrote the code, based on an old processing sketch I remember seeing.

Here's a demo (click the "result" tab):

Here's a walkthrough of how it's set up.

First, initiate snap:

```
s: Snap("#svg"),
```

where #svg is a reference to an empty svg element with ID #svg.

Then initiate a config object that will store all your static variables.

```
drawingConfig: {
circles: {
amount: 20,
sizeMin: 10, //min radius of circle
sizeMax: 20, //max radius of circle
proximity: 100, //distance at which a line will be drawn between neighboring circles
circleArray: [] //stores all the circles on screen
},
canvas: {
width: 800,
height: 600
}
},
```

I made a couple helper functions that I knew I'd be calling often:

```
setIncline: function(){
return { incX: this.randomNumber(-5,5), incY: this.randomNumber(-5,5) }
},
randomNumber: function(min,max){
return Math.floor(Math.random()*(max-min+1)+min);
},
getBounds: function(shape){
shapeBox = shape.node.getBoundingClientRect();
}
```

These simple set the slope at which a ball will travel (its trajectory), generate a random number between X and Y, and get the bounding box of each circle to determine distances / collisions / etc.

Then, you need to draw the circles to the screen:

```
makeCircles: function(){
for (var i=0; i<this.drawingConfig.circles.amount;i++){
var circleX = this.randomNumber(0, this.drawingConfig.canvas.width); //random coordinates for each circle
var circleY = this.randomNumber(0, this.drawingConfig.canvas.height);
var circleRadius = this.randomNumber(this.drawingConfig.circles.sizeMin,this.drawingConfig.circles.sizeMax);
var circleFill = '#'+Math.floor(Math.random()*16777215).toString(16); //generates a random hex value
var circleShape = this.s.circle(circleX, circleY, circleRadius);
circleShape.attr({
fill: circleFill
});
this.drawingConfig.circles.circleGroup.add(circleShape);
var circleIncline = this.setIncline(); //set the trajectory of the ball
var circleObj = { incline: circleIncline, shape: circleShape }; //create an object which will hold both the svg shape itself, and keep track of its incline
this.drawingConfig.circles.circleArray.push(circleObj); //store each circle in an array so we can reference it later
}
this.update();
},
```

So once we place all the circles on the screen, we call our update function, which will run in a loop to animate all the circles, draw all the lines, and detect collisions:

```
update: function(){
var lines = Snap.selectAll('line');
lines.remove(); //remove all the lines that are currently on screen
for (var i=0; i<this.drawingConfig.circles.amount; i++){ //for each circle on screen...
var circle = this.drawingConfig.circles.circleArray[i];
var circleX = circle.shape.node.cx.animVal.value; //get its position...
var circleY = circle.shape.node.cy.animVal.value;
this.move(circle,circleX,circleY);
for (var j=0;j<i;j++){ //and compare it to the position of all the other circles...
if (i != j){ //this makes sure we're not comparing the circle to itself...
var circle2 = this.drawingConfig.circles.circleArray[j];
var circle2X = circle2.shape.node.cx.animVal.value; //get the circles x and y position
var circle2Y = circle2.shape.node.cy.animVal.value;
var dist = mainModule.distance(circleX,circleY,circle2X,circle2Y); //calls a function that will measure the distance between the two circles
if (dist <= mainModule.drawingConfig.circles.proximity){ //if the circles are closer than the range we specified in our configuration object...
var line = mainModule.s.line(circleX, circleY, circle2X, circle2Y).attr({stroke: '#a6a8ab', strokeWidth: '1px'}); //draw a line between them
}
if (dist <= 10) { //collision //if the circles are touching, we have a collision, so we should change the circles' trajectories
circle.incline = mainModule.setIncline();
circle2.incline = mainModule.setIncline();
}
}
}
}
setTimeout(function(){ mainModule.update(); },10); //run this loop every 10ms
},
```

`.attr('cx')`

- but it returns it as a string, which I'd have to parse as an integer, and that seemed like it would slow things down. Three is that this could probably be running using `requestAnimationFrame`

. Anyway, here's the distance function:```
distance: function(circleX,circleY,circle2X,circle2Y){
var distX = circle2X - circleX;
var distY = circle2Y - circleY;
distX = distX*distX;
distY = distY*distY;
return Math.sqrt(distX + distY);
},
```

Again - this could be optimized, but we're basically taking the distance between the two x coordinates and squaring it, as well as the y, and then returning the square root of those two distances added together - pythogorean theorem - a2+b2 = c2. Lastly, we just need to look at the function responsible for moving the circles:

```
move: function(circle,curX,curY){
if (curX > this.drawingConfig.canvas.width || curX < 0) { //if we're outside of the boundaries of our svg canvas...
circle.incline.incX = -circle.incline.incX; //reverse the slope so it heads the opposite direction
}
if (curY > this.drawingConfig.canvas.height || curY < 0) {
circle.incline.incY = -circle.incline.incY;
}
curX = curX + circle.incline.incX;
curY = curY + circle.incline.incY;
if (curX > this.drawingConfig.canvas.width) {
curX = this.drawingConfig.canvas.width;
circle.incline = this.setIncline();
} else if (curX < 0) {
curX = 0;
circle.incline = this.setIncline();
}
if (curY > this.drawingConfig.canvas.height) {
curY = this.drawingConfig.canvas.height;
circle.incline = this.setIncline();
} else if (curY < 0) {
curY = 0;
circle.incline = this.setIncline();
}
circle.shape.attr({ cx: curX, cy: curY }); //set the position
},
```

So here, I could do a much more realistic collision equation, rather than just reversing direction, but this will work for now. Also, there are instances where the circles got stuck on the side of the screen - I probably need some padding in there when, if the circle goes beyond the screen boundaries, I set it equal to a distance within the boundaries (not the boundary itself) so it has somewhere else to animate to on the next cycle. But again, this will work for demonstration. And that's collision. If you can suggest improvements, I'd really appreciate it. Here's a demo of the whole thing working, with complete code .