HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

2D colliding disks in JavaScript (ideal billiard balls)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
idealjavascriptballsbilliardcollidingdisks

Problem

I am implementing a simulation of colliding disks (ideal 2D billiard balls) in JavaScript.

I follow an event-driven algorithm that avoids discretizing time; the algorithm goes as follows at each step:

  • Compute the moment of the next collision (with walls or between two disks)



  • Translate the spheres during that time interval



  • Update the disks velocities following the collision, and start again



I use JavaScript and d3.js; having little experience with these, I would appreciate a review of the code, in particular:

  • How I made the code loop (the loop function is called once the transitions have all ended).



  • If there is another structuring to the code that would make it more clear / efficient.



Here is a working snippet.





svg {
border:1px solid black;
}

var timescale = 800.;
var width = 300,
height = 300,
radius = 0.15;
var svg = d3.select("body").append("svg")
.attr("class", "disk")
.attr("width", width)
.attr("height", height)
.attr("viewBox", "0 0 1 1");

var c10 = d3.scale.category10();

var init_data = [[radius, 0.25, 0.25, Math.random() - 0.5, Math.random() - 0.5],
[radius, 0.75, 0.25, Math.random() - 0.5, Math.random() - 0.5],
[radius, 0.25, 0.75, Math.random() - 0.5, Math.random() - 0.5],
[radius, 0.75, 0.75, Math.random() - 0.5, Math.random() - 0.5]
];

var circles = svg.selectAll(".disk")
.data(init_data)
.enter().append("circle")
.attr("r", function(d) { return d[0] })
.attr("cx", function(d) { return d[1] })
.attr("cy", function(d) { return d[2] })
.attr("fill", c10);

function next_event(circles) {
var nevent = [Infinity];
var data = circles.data();
for (var i = 0; i = 0 ? (1. - r) : r) - x ) / dx;
if (dt = 0 ? (1. - r) : r) - y ) / dy;
if (dt = 0 && scalarprod

Solution

Some issues and solutions:

-
Using arrays for key-value data

What does each element of one of the init_data mean? What is accessed by data[j][4]? Instead of modelling each circle as an array with the fields stored as indices 04, it would be clearer to make each an object with named fields radius, x, y, dx and dy. You can initialise them about as concisely (but more clearly) by declaring a function to construct them from arguments, and calling that 4 times.

-
Repetitive .attr calls

D3 supports passing in an object to selection.attr. It's equivalent to 4 separate calls:

[ ... ]
.append('circle').attr({
    r:  function(d) { return d[0] },
    cx: function(d) { return d[1] },
    cy: function(d) { return d[2] },
    fill: c10
})


-
Verbose vector logic

All that nested arithmetic for the collision detection is currently harder than necessary to understand. A vector library would have the right classes and abstract operations built into it to make all those into just a few lines.

-
Calling circles.each to update data

In D3-land the assumption is usually that selections don't modify data, only read and render it. You could instead call init_data.forEach, which is logically equivalent, but clearer about what arrays contents are being changed and going with existing expectations.

-
Non-commented collision detection logic

Your question's explanation is good, but that explanation should be in the code. It is much easier to read vector arithmetic when you understand the general idea of what it's trying to do. Kudos for all of the ones you have already (like // x-wall, etc): debugging other people's collision code is so painful that any comments at all are are like desert oases.

-
Non-commented transition endall trick

That's super nasty, but I know it's necessary! :) It might be best to link to that answer everybody probably copypastes the implementation from, for the benefit of readers of your code who haven't yet faced this edge case.

Code Snippets

[ ... ]
.append('circle').attr({
    r:  function(d) { return d[0] },
    cx: function(d) { return d[1] },
    cy: function(d) { return d[2] },
    fill: c10
})

Context

StackExchange Code Review Q#124225, answer score: 2

Revisions (0)

No revisions yet.