Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active July 2, 2018 13:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fil/2cec616f64cda2cdb0e7302c9b1be3a5 to your computer and use it in GitHub Desktop.
Save Fil/2cec616f64cda2cdb0e7302c9b1be3a5 to your computer and use it in GitHub Desktop.
Swarmalator
license: mit
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.5/dat.gui.js"></script>
<script>
// Controls
const gui = new dat.GUI();
const controls = {
N: 100,
J: 1,
K: -0.1,
temperature: false,
};
for (i in controls) gui.add(controls, i)
const width = 960,
height = 500,
margin = 10,
tau = 2 * Math.PI;
const M = width - height;
const X = d3.scaleLinear().domain([-2,2]).range([margin + M/2, width - margin - M/2]),
Y = d3.scaleLinear().domain([-2,2]).range([margin, height - margin]),
F = (h) => d3.hsl(h * 360 / tau, 0.9, 0.5, 1),
G = d3.interpolateInferno;
var nodes = d3.range(2).map(function() {
return {
x: 1 - 2 * Math.random(),
y: 1 - 2 * Math.random(),
f: Math.random() * tau,
};
});
const swarmalator = function() {
return function (alpha) {
nodes.forEach((d ,i )=> {
d.vx = d.vy = 0;
});
var n = nodes.length;
nodes.forEach((d, i) => {
for (var j = i+1; j < n; j++) {
var e = nodes[j];
var dx = d.x - e.x,
dy = d.y - e.y,
df = d.f - e.f,
dist2 = dx * dx + dy * dy,
dist = Math.sqrt(dist2);
var mu = ((1 + controls.J * Math.cos(df)) * dist - 1) / dist2 / n;
d.vx -= dx * mu;
d.vy -= dy * mu;
e.vx += dx * mu;
e.vy += dy * mu;
d.f -= Math.sin(df) / dist * controls.K / n;
e.f += Math.sin(df) / dist * controls.K / n;
}
});
};
}
const simulation = d3.forceSimulation()
.alphaDecay(1e-12)
.nodes(nodes)
.on("tick", ticked);
simulation.force('swarm', swarmalator());
const canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height)
.on('mousemove click', function() {
simulation.alpha(0.1).restart();
});
const context = canvas.node().getContext("2d");
function ticked() {
if (nodes.length < controls.N) nodes.push({
x: 1 - 2 * Math.random(),
y: 1 - 2 * Math.random(),
f: Math.random() * tau,
});
if (nodes.length > controls.N) nodes = nodes.slice(1, nodes.length);
simulation.nodes(nodes);
const r = 0.25 * height / Math.sqrt(nodes.length);
context.clearRect(0, 0, width, height);
for (var i = 0, n = nodes.length; i < n; ++i) {
var node = nodes[i];
context.beginPath();
context.moveTo(X(node.x), Y(node.y));
context.arc(X(node.x), Y(node.y), r, 0, tau);
if (controls.temperature) {
context.fillStyle = G(4 * Math.sqrt(Math.sqrt(node.vx * node.vx + node.vy * node.vx))) ;
} else {
context.fillStyle = F(node.f);
}
context.fill();
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment