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