Skip to content

Instantly share code, notes, and snippets.

@christophermanning
Last active November 3, 2019 04:00
Show Gist options
  • Save christophermanning/4491156 to your computer and use it in GitHub Desktop.
Save christophermanning/4491156 to your computer and use it in GitHub Desktop.
Flow Field
<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment