Skip to content

Instantly share code, notes, and snippets.

@blinsay
Last active June 14, 2020 13:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save blinsay/c8bcfeff0b6159f44aec to your computer and use it in GitHub Desktop.
Save blinsay/c8bcfeff0b6159f44aec to your computer and use it in GitHub Desktop.
Randomized Contour Lines

Randomly placed actors explore a 2d function by walking along contour lines. Perlin noise courtesy of d2fn.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
background: rgb(255, 255, 255);
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="/d2fn/raw/8777d620caef32959713/perlin.js"></script>
<script>
var TWO_PI = Math.PI * 2,
PI_OVER_TWO = Math.PI / 2,
INFINITY = 1 / 0;
var width = 960,
height = 500,
noiseScale = 0.003,
numContours = 200,
step = 0,
steps = 5000,
stepLength = 0.5,
turnAngle = TWO_PI / 32;
var context = setupCanvas(width, height),
noise = perlin.noise(noiseScale),
easing = easeAlpha(steps);
var contours = [];
for (var i = 0; i < numContours; i++) {
contours.push(randomContour(width, height, noise, stepLength, turnAngle));
}
d3.timer(function() {
if (++step > steps) {
console.log("done with animation");
return true;
}
context.lineWidth = 1;
context.globalCompositeOperation = 'lighter';
context.beginPath();
var alpha = easing.get();
contours.forEach(function(contour) {
var ln = contour.step();
context.strokeStyle = "rgba(49, 130, 189," + alpha + ")";
context.moveTo(ln.x1, ln.y1);
context.lineTo(ln.x2, ln.y2);
});
context.stroke();
easing.step();
})
// Append a (width x height) canvas element to the body and return its context
// object.
function setupCanvas(width, height) {
var canvas = d3.select("body")
.append("canvas")
.attr({width: width, height: height});
var context = canvas.node().getContext("2d");
context.fillStyle = "rgb(0, 0, 0)";
context.fillRect(-1, -1, width, height);
return context;
}
// An object that eases opacity as steps progress.
function easeAlpha(steps) {
var n = 0, alpha = 0;
return {
step: function() { alpha = 0.3 + 0.125 * (1 - Math.cos(2 * Math.PI * (n++ / steps))) },
get: function() { return alpha }
}
}
// Returns an object with a step() method that walks in 2d space while trying
// to keep f(x, y) as close to f(x_start, y_start) as possible. The object only
// walks "forward" - objects maintain a current heading, and only look for new
// points in a +/- 90 arc.
function randomContour(width, height, f, stepLength, turnAngle, minimumStep) {
var x = Math.random() * width,
y = Math.random() * height,
heading = Math.random() * TWO_PI,
currentValue = f(x, y);
return {
step: function() {
var x1 = x, y1 = y;
var minDelta = INFINITY,
minHeading = heading,
minF = -1,
minX = -1,
minY = -1;
for (var theta = heading - PI_OVER_TWO; theta < heading + PI_OVER_TWO; theta += turnAngle) {
var newX = x + stepLength * Math.cos(theta),
newY = y + stepLength * Math.sin(theta),
newValue = f(newX, newY),
delta = Math.abs(newValue - currentValue);
if (delta < minDelta) {
minDelta = delta;
minHeading = theta;
minF = newValue;
minX = newX;
minY = newY;
}
}
x = minX;
y = minY;
heading = minHeading;
currentValue = minF;
return {x1: x1, y1: y1, x2: x, y2: y}
}
}
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment