Skip to content

Instantly share code, notes, and snippets.

@tonyhschu
Last active October 18, 2016 04:56
Show Gist options
  • Save tonyhschu/cc0518056e9a7992dc05bea80096ff05 to your computer and use it in GitHub Desktop.
Save tonyhschu/cc0518056e9a7992dc05bea80096ff05 to your computer and use it in GitHub Desktop.
Spiraling Birds
license: mit
<!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