An iteration on a @micahstubbs iteration on a procedural art project from @randylubin
Last active
October 15, 2017 19:25
-
-
Save johnburnmurdoch/0405a9e1c890f3ba5a4eac7a0ab1d61f to your computer and use it in GitHub Desktop.
Procedurally generated ribbons, using Bézier curve iterations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: Apache-2.0 | |
height: 900 | |
border: no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en-GB"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width,user-scalable=no"> | |
<title>curveArt</title> | |
<script src="https://unpkg.com/d3/build/d3.min.js"></script> | |
<script src="https://unpkg.com/d3-selection-multi"></script> | |
<script src="https://unpkg.com/d3-scale-chromatic"></script> | |
<script src="https://josephg.com/perlin/3/perlin.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.3/seedrandom.min.js"></script> | |
<style> | |
</style> | |
</head> | |
<body> | |
<canvas id=canvas></canvas> | |
<script type=text/javascript charset=UTF-8> | |
let canvas = d3.select("canvas"), | |
ctx = canvas.node().getContext('2d'), | |
width = 1000, | |
height = 900, | |
DPR = window.devicePixelRatio || 1, | |
scaledWidth = width * DPR, | |
scaledHeight = height * DPR; | |
canvas | |
.attrs({ | |
width: scaledWidth, | |
height: scaledHeight | |
}) | |
.styles({ | |
width: width + "px", | |
height: height + "px" | |
}); | |
// Config array takes seven arguments: | |
// 1. Total duration of drawing stage | |
// 2. Frame-rate (the given number of frames per second, or the closest your browser can get) | |
// 3. A seed for our random number generator. Using the same seed again will yield exactly the same results | |
// 4. One of d3's inbuilt colour scales | |
// 5. The canvas background colour | |
// 6. Alpha (transparency/opacity) value for the lines. Can be very low as there's lots of over-painting | |
// 7. Number of lines to draw. Depending on your machine's performance (and your aesthetic tastes), best results tend to come with anything between 2 and 15 | |
// Some particularly striking combinations of random seed and colour palette: | |
// config = [10000, 60, 25, "Magma", "#212121", 0.01 ,15], | |
// config = [5000, 120, 40, "Cool", "#212121", 0.01 ,15], | |
// config = [10000, 60, 40, "Plasma", "#212121", 0.01 ,15], | |
// config = [5000, 60, 41, "Rainbow", "#ffffff", 0.01, 10], | |
// config = [5000, 20, 40, "Inferno", "#ffffff", 0.01, 15], | |
// config = [5000, 60, 102, "Inferno", "#ffffff", 0.03, 3], | |
// config = [5000, 60, 2, "Viridis", "#ffffff", 0.02, 5], | |
// config = [3000, 120, 49, "Rainbow", "#212121", 0.08, 2], | |
// config = [5000, 30, 58, "Cool", "#212121", 0.05, 7], | |
let | |
config = [10000, 60, 25, "Magma", "#212121", 0.01 ,15], | |
duration = config[0], | |
fps = config[1], | |
palette = config[3], | |
alpha = config[5], | |
bg = config[4], | |
tickDuration = 1000/fps, | |
lines = [], | |
nLines = config[6]; | |
Math.seedrandom(config[2]); | |
let randomSeed = Math.random(); | |
noise.seed(Math.random()); | |
ctx.fillStyle = bg; | |
ctx.fillRect(0,0,scaledWidth,scaledHeight); | |
let noiseScale = d3.scaleLinear().range([0,1]).domain([-1,1]); | |
let colourScale = d3.scaleSequential(d3["interpolate" + palette]).domain([0,1]); | |
function lineData(n){ | |
for(let i = 0; i < n; i++){ | |
let col = d3.rgb(colourScale(Math.random())); | |
let r = col.r; | |
let g = col.g; | |
let b = col.b; | |
let x1 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())), | |
x2 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())), | |
x3 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())), | |
x4 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())), | |
y1 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())), | |
y2 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())), | |
y3 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())), | |
y4 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())); | |
let colorString = `rgba(${r}, ${g}, ${b}, ${alpha})`; | |
let lineData = [x1,x2,x3,x4, y1,y2,y3,y4, colorString]; | |
lineData[9] = (x1 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random()))); | |
lineData[10] = (x2 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random()))); | |
lineData[11] = (x3 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random()))); | |
lineData[12] = (x4 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random()))); | |
lineData[13] = (y1 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random()))); | |
lineData[14] = (y2 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random()))); | |
lineData[15] = (y3 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random()))); | |
lineData[16] = (y4 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random()))); | |
lines.push(lineData); | |
} | |
let xMin = d3.min(lines.map(d => d.slice(0,4).concat(d.slice(9,13))).reduce((previous, current) => previous.concat(current))), | |
xMax = d3.max(lines.map(d => d.slice(0,4).concat(d.slice(9,13))).reduce((previous, current) => previous.concat(current))), | |
yMin = d3.min(lines.map(d => d.slice(4,8).concat(d.slice(13,17))).reduce((previous, current) => previous.concat(current))), | |
yMax = d3.max(lines.map(d => d.slice(4,8).concat(d.slice(13,17))).reduce((previous, current) => previous.concat(current))); | |
let xScale = d3.scaleLinear().range([0, scaledWidth]).domain([xMin, xMax]), | |
yScale = d3.scaleLinear().range([0, scaledHeight]).domain([yMin, yMax]); | |
lines.forEach(function(l,li){ | |
l.forEach(function(d,i){ | |
if([0,1,2,3,9,10,11,12].indexOf(i) >= 0){ | |
lines[li][i] = xScale(d); | |
}else if(i == 8){ | |
d = d; | |
}else if([0,1,2,3,9,10,11,12].indexOf(i) < 0){ | |
lines[li][i] = yScale(d); | |
} | |
}) | |
}); | |
drawLines(); | |
} | |
function drawLines(){ | |
lines.forEach(function(l){ | |
ctx.strokeStyle = l[8]; | |
ctx.lineWidth = 3; | |
ctx.beginPath(); | |
ctx.moveTo(l[0], l[4]); | |
ctx.bezierCurveTo(l[1], l[5], l[2], l[6], l[3], l[7]); | |
ctx.stroke(); | |
let timer = d3.interval(function(elapsed) { | |
let p = elapsed/duration; | |
update(p); | |
if (elapsed > duration) timer.stop(); | |
}, tickDuration); | |
}) | |
} | |
function update(t){ | |
function interpolatePoint(data, index){ | |
return data[index] + (data[index+8] - data[index]) * t | |
} | |
lines.forEach(d => { | |
ctx.strokeStyle = d[8]; | |
ctx.beginPath(); | |
ctx.moveTo(interpolatePoint(d,0), interpolatePoint(d,4)); | |
ctx.bezierCurveTo(interpolatePoint(d,1), interpolatePoint(d,5), interpolatePoint(d,2), interpolatePoint(d,6), interpolatePoint(d,3), interpolatePoint(d,7)); | |
ctx.stroke(); | |
ctx.closePath(); | |
}); | |
} | |
lineData(nLines); | |
// canvas.on("dblclick", function(){ | |
// drawLines(); | |
// }); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment