Modelling Collision Detection in D3 with tween functions.

Despite being named after a terribly awkward developmental stage, tween functions can let you do some really cool stuff in d3.

The tween function is run on an element during a d3 transition. It allows you to register a custom function that will be run at every increment between the two poles of that transition and get reference to the precise attributes of the element each step of the way.

I came across tween functions to model collision detection when I was trying to implement a rudimentary Asteroids clone. For this example, I'll boil it down a little more, and use a custom tween function to determine when to randomly moving circles intersect each other.

Set Up d3

To get things going we're going to set up the svg element and append it. We'll also add some data for d3 to join for some sweet circles.

  var svg = d3.select('#container')
    .append('svg')
    .attr('height', 600)
    .attr('width', 1000);

  var data = ['blue', 'purple'];

Set Up Animation

Next up, we're going to set up a simple animation to have our circles bounce around between random points on the screen. We'll chain the animations by calling .each('end', move) to re-call the move function when the original transition is completed.

  var move = function() {
    var circles = svg.selectAll('circle')
      .data(data);

    circles.enter()
      .append('circle')

    var circleAttrs = circles
      .transition()
      .duration(1000)
      .attr('cx', function(d) {
        return Math.floor(Math.random() * 1000);
      })
      .attr('cy', function(d) {
        return Math.floor(Math.random() * 600);
      })
      .attr('r', 100)
      .attr('fill', function(d) {
        return d;
      })
      .tween('collision', collisionDetection)
      .each('end', move);
  };

  move();

Enter Tween Function

Notice right before the final call to .each, we add our tween function. The first argument is the name the tween function is registered for, and the second argument is a factory function that will return a function to be run on all the increments between the start and the end of the transition.

The factory function passed to tween must return a function. It's also possible to take advantage of the closure nature of the factory function to preserve and reference any information from the start of the transition.

Our factory function, collisionDetection will return a function that will grab all other present circles using d3.select, and using some slick 8th grade Pythagorean algebra, determine the distance between our circle and any others. If the calculated distance is less than the sum of their two radii, we'll consider them collided.

var collisionDetection = function() {  
    return function() {
      var thisCircle = d3.select(this);

      d3.select('circle').each(function() {
        var otherCircle = d3.select(this);

        if (thisCircle.datum() !== otherCircle.datum()) {

          dx = thisCircle.attr('cx') - otherCircle.attr('cx'),
            dy = thisCircle.attr('cy') - otherCircle.attr('cy'),
            distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));

          if (distance < +thisCircle.attr('r') + +otherCircle.attr('r')) {
            collision(thisCircle, otherCircle);
          }
        }
      });
    };
  };

In this example, on a collision it will call a collision function passing in the two offending circles. Throw in a quick console.log to see what's going on.

  var collision = function(thisCircle, otherCircle) {
    console.log('Collision at: ' + thisCircle.attr('cx') + ',' + thisCircle.attr('cy'));
  }

tween functions give you a lot of room to play around when using transitions in d3. Check out the documentation and make some sweet stuff.