Created by Christopher Manning
Animation prototype.
Created by Christopher Manning
Animation prototype.
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Flow Field</title> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/processing.js/1.4.1/processing-api.min.js"></script> | |
| <script src="http://bl.ocks.org/d/4289018/simplex-noise.min.js"></script> | |
| <!--<script type="text/javascript" src="/js/d3/d3.min.js"></script>--> | |
| <!--<script type="text/javascript" src="/js/dat-gui/build/dat.gui.js"></script>--> | |
| <!--<script type="text/javascript" src="/js/processing-js/processing.js"></script>--> | |
| <!--<script type="text/javascript" src="/js/simplex-noise.min.js"></script>--> | |
| <style type="text/css"> | |
| body { | |
| padding: 0; | |
| margin: 0; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script type="text/javascript"> | |
| var Vector = Processing.prototype.PVector | |
| config = {"x": 100, "y": 100, "maxForce": .1, "maxSpeed": 3, "grid": false, "followMouse": false, "freedom": .75} | |
| gui = new dat.GUI() | |
| gui.add(config, "freedom", 0, 1) | |
| xChanger = gui.add(config, "x", 1, 250) | |
| xChanger.onChange(function(value) { | |
| setNoise() | |
| }) | |
| yChanger = gui.add(config, "y", 1, 250) | |
| yChanger.onChange(function(value) { | |
| setNoise() | |
| }) | |
| gui.add(config, "maxForce", 0, .5) | |
| gui.add(config, "maxSpeed", 0, 15) | |
| gridChanger = gui.add(config, "grid") | |
| gridChanger.onChange(function(value) { | |
| field.attr("display", value ? "block" : "none") | |
| }) | |
| margin = {top: 0, left: 0, bottom: 0} | |
| size = 20 | |
| height = window.innerHeight | |
| width = window.innerWidth | |
| svg = d3.select("body") | |
| .append("svg") | |
| .attr("fill", "none") | |
| .attr("width", width) | |
| .attr("height", height) | |
| flowField = [] | |
| flowField.lookup = function(x, y) { | |
| var x = this[mod((x-mod(x,size))*1, width)] | |
| if(typeof(x) == "undefined") return false | |
| var y = x[mod((y-mod(y,size))*1, height)] | |
| if(typeof(y) == "undefined") return false | |
| return y | |
| } | |
| g = svg.append("g") | |
| .attr("transform", "translate(" + margin.left + "," + margin.top + ")") | |
| grid = g.append("g").attr("id", "grid") | |
| simplex = new SimplexNoise(); | |
| simplexScale = d3.scale.linear().domain([-1, 1]).range([0, 2*Math.PI]) | |
| data = [] | |
| for (var y = 0; y < height; y+=size) { | |
| for (var x = 0; x < width; x+=size) { | |
| if(data.length > 10000) break | |
| data.push({location: new Vector(x, y)}) | |
| } | |
| } | |
| field = grid.selectAll("path.field") | |
| .data(data) | |
| .enter().append("path") | |
| .attr("display", config["grid"] ? "block" : "none") | |
| .attr("class", "field") | |
| .attr("d", "M2,3L7,4.5L2,6") | |
| .attr("d", "M0,4.5L7,4.5L5,2L7,4.5L5,7L7,4.5") | |
| .attr("stroke-width", "1") | |
| .attr("stroke-linejoin", "round") | |
| .attr("stroke", "black") | |
| .attr("fill", "none") | |
| setNoise() | |
| function setNoise() { | |
| field | |
| .each(function(d) { | |
| theta = simplexScale(simplex.noise2D(d.location.x/config["x"], d.location.y/config["y"])) | |
| d.vector = new Vector(Math.cos(theta), Math.sin(theta)) | |
| if(typeof(flowField[d.location.x]) == "undefined") flowField[d.location.x] = [] | |
| flowField[d.location.x][d.location.y] = d.vector | |
| }) | |
| .attr("transform", function(d) { return vectorTransform(d.location, d.vector) }) | |
| } | |
| // field mouse | |
| var mouse = new Vector(0, 0) | |
| svg.on("mousemove", function() { | |
| if(config["followMouse"]) { | |
| mouse.set(d3.event.x, d3.event.y) | |
| field | |
| .attr("transform", function(d) { | |
| d.vector.set(Vector.sub(mouse, d.location)) | |
| d.vector.normalize() | |
| return vectorTransform(d.location, d.vector) | |
| }) | |
| } | |
| }) | |
| svg.on("mouseover", function() { | |
| config["followMouse"] = true | |
| }) | |
| svg.on("mouseout", function() { | |
| config["followMouse"] = false | |
| setNoise() | |
| }) | |
| // boids | |
| boids = g.append("g").attr("id", "boids") | |
| .selectAll("path.boid") | |
| .data(data.map(function(d) { | |
| nd = {} | |
| nd.location = new Vector(d.location.x, d.location.y) | |
| nd.velocity = new Vector(0, 0) | |
| nd.acceleration = new Vector(0, 0) | |
| return nd | |
| })) | |
| .enter().append("path") | |
| .attr("d", "M2,3L7,4.5L2,6L2,3") | |
| .attr("stroke-width", ".1") | |
| .attr("stroke", "black") | |
| var desired = new Vector(0, 0) | |
| d3.timer(function(t) { | |
| boids | |
| .attr("transform", function(d) { | |
| // follow | |
| if(Math.random() > config["freedom"]){ | |
| var desiredRef = flowField.lookup(d.location.x, d.location.y) | |
| desired.set(desiredRef.x, desiredRef.y) | |
| desired.mult(config["maxSpeed"]) | |
| desired.sub(d.velocity) | |
| desired.limit(config["maxForce"]) | |
| d.acceleration.add(desired) | |
| } | |
| // update | |
| d.velocity.add(d.acceleration) | |
| d.velocity.limit(config["maxSpeed"]) | |
| d.location.add(d.velocity) | |
| d.acceleration.mult(0) | |
| // wraparound | |
| d.location.set(mod(d.location.x, width), mod(d.location.y, height)) | |
| return vectorTransform(d.location, d.velocity) | |
| }) | |
| }) | |
| function vectorTransform(location, velocity) { | |
| transform = "" | |
| if(typeof(location) != "undefined") transform += "translate("+location.x+" "+location.y+") " | |
| if(typeof(velocity) != "undefined") transform += "rotate("+(Math.atan2(velocity.y, velocity.x) * 180 / Math.PI)+" 4.5 4.5)" | |
| return transform | |
| } | |
| //http://stackoverflow.com/a/3417242 | |
| function mod(i, i_max) { | |
| return ((i % i_max) + i_max) % i_max; | |
| } | |
| </script> | |
| </body> | |
| </html> |