block by tonyhschu 713fe6b5ea3c7814da63719bc12c3a1e

Boid Sort of Works

Full Screen

Built with blockbuilder.org

index.html

<!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>