Built with blockbuilder.org
Last active
October 18, 2016 04:56
-
-
Save tonyhschu/713fe6b5ea3c7814da63719bc12c3a1e to your computer and use it in GitHub Desktop.
Boid Sort of Works
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
</style> | |
</head> | |
<body> | |
<script> | |
console.clear() | |
// Feel free to change or delete any of the code you see in this editor! | |
var NUM_OF_NODES = 130 | |
var WIDTH = 960 | |
var HEIGHT = 500 | |
var TICKS = 0 | |
var LOCAL_DIST = 30 | |
var TAU = 2 * Math.PI | |
var mouse = [0, 0] | |
var forceX = d3.forceX().strength(0.01) | |
var forceY = d3.forceY().strength(0.01) | |
function sigmoid(t) { | |
return 1/(1+Math.pow(Math.E, -t)); | |
} | |
function normalize(theta) { | |
var t = theta % TAU | |
if (t > Math.PI) { return t - TAU } | |
if (t < -Math.PI) { return t + TAU } | |
return t | |
} | |
var test = Math.PI | |
function subtractAngle(a, b) { | |
return normalize(normalize(a) - normalize(b)) | |
} | |
var forceSpiral = function(alpha) { | |
for (var i = 0, n = nodes.length, node, k = alpha * 0.1; i < n; ++i) { | |
node = nodes[i]; | |
var dx = node.x - mouse[0] | |
var dy = node.y - mouse[1] | |
var dist = Math.sqrt(dx * dx + dy * dy) | |
var dForce = (sigmoid((dist - 80) / 60) - 0.5) | |
//var dForce = (sigmoid((dist - 150) / 20) - 0.5) * -2 | |
var backgroundTheta = Math.atan2(dy, dx) + Math.PI / 2 + Math.PI * dForce | |
var dt = subtractAngle(node.theta, backgroundTheta) | |
node.theta = normalize(node.theta + dt * 0.1 * Math.abs(dForce)) | |
node.vx += Math.cos(node.theta) * -node.velocity * k | |
node.vy += Math.sin(node.theta) * -node.velocity * k | |
var r = Math.round(123 - dForce * 123) | |
var g = Math.round(123 + dForce * 60) | |
node.color = 'rgb('+ r + ',' + g + ', 0)'; | |
} | |
} | |
var forceB = function(alpha) { | |
for (var i = 0, n = nodes.length, node, k = alpha * 0.1; i < n; ++i) { | |
node = nodes[i]; | |
var local = nodes.filter(function(n) { | |
var sightX = node.x + Math.cos(node.theta) * 12 | |
var sightY = node.y + Math.sin(node.theta) * 12 | |
var dx = n.x - sightX | |
var dy = n.y - sightY | |
var dist = Math.sqrt(dx * dx + dy * dy) | |
return dist < LOCAL_DIST | |
}) | |
if (local.length > 0) { | |
var localCentroid = local.reduce(function(prev, curr) { | |
return [prev[0] + curr.x, prev[1] + curr.y] | |
}, [0, 0]) | |
.map(function(sum) { | |
return sum / local.length | |
}) | |
var localAlignment = local.reduce(function(prev, curr) { | |
return prev + curr.theta | |
}, 0) / local.length | |
var ld = subtractAngle(node.theta, localAlignment) // cohesion delta | |
var cx = node.x - localCentroid[0] | |
var cy = node.y - localCentroid[1] | |
var distFromCentroid = Math.sqrt(cx * cx + cy * cy) | |
var cohesion = Math.atan2(node.y - localCentroid[1], node.x - localCentroid[0]) | |
var cd = subtractAngle(node.theta, cohesion) // cohesion delta | |
var cohesionPower = sigmoid(distFromCentroid - 120) / 2 | |
node.theta += cd * cohesionPower | |
// node.theta += ld * 0.005 | |
} | |
} | |
} | |
var simulation = d3.forceSimulation() | |
.force("collide",d3.forceCollide(function(d){ return d.r }).iterations(16)) | |
.force("boid", forceB) | |
.force("spiral", forceSpiral) | |
.alphaDecay(0) | |
var svg = d3.select("body").append("svg") | |
.attr("style", "background: #333") | |
.attr("width", WIDTH) | |
.attr("height", HEIGHT) | |
.on("mousemove", function() { | |
mouse = d3.mouse(this); | |
forceX.x(mouse[0]) | |
forceY.y(mouse[1]) | |
simulation.alpha(1) | |
simulation.restart(); | |
}) | |
var nodes = d3.range(NUM_OF_NODES).map(function(key) { | |
return { | |
key: key, | |
x: WIDTH / 2, | |
y: HEIGHT / 2, | |
r: 5, | |
theta: Math.random() * Math.PI * 2, | |
velocity: 12 | |
} | |
}) | |
var triangles = svg.append("g") | |
.attr("class", "circles") | |
.selectAll(".triangle") | |
.data(nodes) | |
.enter().append("g") | |
.attr("class", "triangle") | |
.each(function(d) { | |
var layer = d3.select(this) | |
layer.append('path') | |
.attr('d', 'M -7, 0 L 5, 5 L 5, -5 Z') | |
}) | |
var paths = svg.selectAll("path") | |
var ticked = function() { | |
TICKS += 1 | |
triangles | |
.attr("transform", function(d) { | |
return "translate(" + d.x + ", " + d.y + ") rotate(" + (d.theta / Math.PI * 180) + ")"; | |
}) | |
paths.each(function(d, i) { | |
d3.select(this).attr('fill', nodes[i].color) | |
}) | |
} | |
simulation | |
.nodes(nodes) | |
.on("tick", ticked); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment