Built with blockbuilder.org
forked from tonyhschu's block: Boid Sort of Works
forked from tonyhschu's block: Spiraling Birds
license: mit |
Built with blockbuilder.org
forked from tonyhschu's block: Boid Sort of Works
forked from tonyhschu's block: Spiraling Birds
<!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> |