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