diff --git a/app/8/index.js b/app/8/index.js new file mode 100644 index 0000000..6fa070f --- /dev/null +++ b/app/8/index.js @@ -0,0 +1,210 @@ +new p5(sketch => { + sketch.disableFriendlyErrors = true; + // reused dimensions and a seed + let seed, width, height, noiseResolution, overdraw, blurQuality; + const layers = {}; // offscreen layers + const shaders = {}; // shaders + const lib = {}; // libraries + const assets = {}; // fonts, images, sound files + + sketch.preload = () => { + shaders.whiteNoise = sketch.loadShader( + "../shaders/base.vert", + "../shaders/white-noise.frag" + ); + assets.stroke = sketch.loadImage("./brush-100x30.png"); + }; + + sketch.setup = () => { + filenamePrefix = "seigler-p5-8-terra-firma-"; + overdraw = 0.1; + width = Math.floor(sketch.windowWidth * (1 + overdraw)); + height = Math.floor(sketch.windowHeight * (1 + overdraw)); + noiseResolution = [0.2, 0.1, 0.05, 2]; + blurQuality = 2; + + window.onhashchange = () => { + seed = window.location.hash.substr(1); + generate(); + }; + + seed = window.location.hash.substr(1); + sketch.colorMode(sketch.HSL, 1); + + sketch.createCanvas(sketch.windowWidth, sketch.windowHeight); + + layers.contours = sketch.createGraphics(width, height); + layers.contours.colorMode(sketch.HSL, 1); + + layers.noise = sketch.createGraphics(width, height, sketch.WEBGL); + + // layers.blur1 = sketch.createGraphics(width, height, sketch.WEBGL); + // layers.blur2 = sketch.createGraphics(width, height, sketch.WEBGL); + + generate(); + }; + + sketch.draw = () => {}; + + sketch.keyReleased = () => { + if (sketch.key == " ") { + seed = null; + generate(); + } else if (sketch.key == "s") { + sketch.saveCanvas(filenamePrefix + seed + ".jpg", "jpg"); + } + }; + + sketch.doubleClicked = () => { + seed = null; + generate(); + }; + + let resizeTimer; + sketch.windowResized = () => { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { + window.location.reload(); + }, 100); + }; + + function generate() { + if (seed) { + sketch.randomSeed(seed); + } else { + const seed = Math.floor(sketch.random(1000000000000)); + window.location.hash = seed; + sketch.randomSeed(seed); + } + + sketch.noiseSeed(sketch.random(0, 1000000000)); + lib.simplex = new SimplexNoise(sketch.random(0, 1000000000)); + + layers.contours.clear(); + drawContours(layers.contours); + + sketch.blendMode(sketch.BLEND); + sketch.background(0); + layers.noise.shader(shaders.whiteNoise); + shaders.whiteNoise.setUniform("u_resolution", [width, height]); + shaders.whiteNoise.setUniform("u_alpha", 0.05); + layers.noise.rect(0, 0, width, height); + sketch.image( + layers.contours, + Math.round((-width * overdraw) / 2), + Math.round((-height * overdraw) / 2) + ); + sketch.blendMode(sketch.OVERLAY); + sketch.image(layers.noise, 0, 0); + } + + function drawContours(layer) { + const noiseScale = 3 / (width + height); + const quant = Math.round(Math.sqrt(width * height) / 1440 * 100); // 100 at 1920*1080 + layers.contours.imageMode(sketch.CENTER); + + const dots = buildGrid(0, 0, width, height, 15 * quant); + layer.noStroke(); + dots.forEach(({x, y}) => { + const {noise, angle} = noisePlus(x, y, noiseScale); + if (noise < 0.5) { // water + layer.tint(218/360, 0.8, 0.1 + sketch.random(0.05) + noise * 2 * 0.2); + } else { // land + layer.tint(119/360, 0.8, 0.1 + sketch.random(0.05) + (noise - 0.5) * 2 * 0.2); + } + const size = 200; + layer.translate(x, y); + layer.rotate(angle + (noise < 0.5 ? sketch.HALF_PI : 0)); + layer.image(assets.stroke, 0, 0, size, size / 3); + layer.resetMatrix(); + }); + + const trails = buildGrid(0, 0, width, height, 5 * quant); + layer.noTint(); + layer.strokeCap(sketch.SQUARE); + trails.forEach((point) => { + const x = point.x + sketch.random(-50, 50); + const y = point.y + sketch.random(-50, 50); + const noise = fractalNoise(x, y, noiseScale); + for (let i = 0, max = quant/2, aX = x, aY = y; i < max; i++) { + const {angle} = noisePlus(aX, aY, noiseScale); + const dX = Math.cos(angle + sketch.HALF_PI) * 4; + const dY = Math.sin(angle + sketch.HALF_PI) * 4; + if (noise > 0.5) { + layer.stroke(0, 0.8); + } else { + layer.stroke(1, 0.5); + } + layer.strokeWeight(Math.sin(i/max*Math.PI)); // smooth 0 to 1 to 0 + layer.line(aX, aY, aX + dX, aY + dY); + aX += dX; + aY += dY; + } + }); + } + + function buildGrid(minX, minY, maxX, maxY, approxPoints) { + const width = maxX - minX; + const height = maxY - minY; + const unit = Math.sqrt(width * height / Math.sin(Math.PI / 3) / approxPoints); + const rows = + Math.max(1, Math.round(height / unit / Math.sin(Math.PI / 3))) + 1; + const cols = Math.max(1, Math.round(width / unit)) + 1; + const grid = []; + for (let index = 0; index < rows * cols; index++) { + const col = index % cols; + const row = Math.floor(index / cols); + grid.push({ + x: minX + (width / (cols - 1)) * col + (((row % 2) - 0.5) * unit) / 2, + y: minY + (height / (rows - 1)) * row + }); + } + return grid; + } + + function noisePlus(x, y, noiseScale = 2 / (width + height), noiseOffset = 0) { + const noise = fractalNoise(x, y, noiseScale, noiseOffset); + const dX = fractalNoise(x + 1, y, noiseScale, noiseOffset) - noise; + const dY = fractalNoise(x, y + 1, noiseScale, noiseOffset) - noise; + const angle = Math.atan2(dY, dX); + const length = sketch.dist(0, 0, dX, dY); + return ({noise, angle, length}); + } + + function fractalNoise(x, y, noiseScale = 2 / (width + height), noiseOffset = 0) { + return 0.5 + 0.5 * ( + lib.simplex.noise2D( + noiseOffset + noiseScale * x, + noiseOffset + noiseScale * y + ) + + 0.5 * lib.simplex.noise2D( + noiseOffset + 2 * noiseScale * x, + noiseOffset + 2 * noiseScale * y + ) + + 0.25 * lib.simplex.noise2D( + noiseOffset + 4 * noiseScale * x, + noiseOffset + 4 * noiseScale * y + ) + ) / 1.75; + } + + // give normally distributed values from 0-1 a uniform distribution + function flattenDistribution(x) { + return ( + 23.8615 * Math.pow(x, 5) - + 59.6041 * Math.pow(x, 4) + + 47.2472 * Math.pow(x, 3) - + 11.3053 * Math.pow(x, 2) + + 0.806219 * x - + 0.00259101 + ); + } + + // returns abs angle from a to b to c + function three_point_angle(A, B, C) { + const AB = Math.sqrt(Math.pow(B.x - A.x, 2) + Math.pow(B.y - A.y, 2)); + const BC = Math.sqrt(Math.pow(B.x - C.x, 2) + Math.pow(B.y - C.y, 2)); + const AC = Math.sqrt(Math.pow(C.x - A.x, 2) + Math.pow(C.y - A.y, 2)); + return Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB)); + } +}); diff --git a/app/8/index.static.hbs b/app/8/index.static.hbs new file mode 100644 index 0000000..873fcd3 --- /dev/null +++ b/app/8/index.static.hbs @@ -0,0 +1,7 @@ +--- +title: Terra Firma +created: 2019-11-18 +index: 8 +_options: + layout: app/layouts/sketch.hbs +--- diff --git a/app/assets/8/brush-100x30.png b/app/assets/8/brush-100x30.png new file mode 100644 index 0000000..18da591 Binary files /dev/null and b/app/assets/8/brush-100x30.png differ diff --git a/app/assets/8/brush-911x185.png b/app/assets/8/brush-911x185.png new file mode 100644 index 0000000..26aede4 Binary files /dev/null and b/app/assets/8/brush-911x185.png differ diff --git a/app/assets/8/example.jpg b/app/assets/8/example.jpg new file mode 100644 index 0000000..e3e9dbe Binary files /dev/null and b/app/assets/8/example.jpg differ diff --git a/app/index.static.hbs b/app/index.static.hbs index 0d83b46..8ffa435 100644 --- a/app/index.static.hbs +++ b/app/index.static.hbs @@ -14,6 +14,12 @@ _options:

P5.js generative art

+ {{> preview + index="8" + date="2019-11-18" + title="Terra Firma" + alt="a brush stroked map of sea and land, with breezy black and white contour lines marking the elevation" + }} {{> preview index="7" date="2019-11-16" diff --git a/app/layouts/sketch.hbs b/app/layouts/sketch.hbs index f1c24fb..6e6bd21 100644 --- a/app/layouts/sketch.hbs +++ b/app/layouts/sketch.hbs @@ -19,6 +19,7 @@ + diff --git a/brunch-config.js b/brunch-config.js index b1a17e8..9601645 100644 --- a/brunch-config.js +++ b/brunch-config.js @@ -21,7 +21,8 @@ exports.plugins = { modules: [ 'node_modules/p5/lib/p5.min.js', 'node_modules/p5/lib/addons/p5.sound.min.js', - 'node_modules/voronoi/rhill-voronoi-core.min.js' + 'node_modules/voronoi/rhill-voronoi-core.min.js', + 'node_modules/simplex-noise/simplex-noise.js' ], verbose : true, onlyChanged: true diff --git a/package-lock.json b/package-lock.json index 6c6d3d4..7b089fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3338,9 +3338,9 @@ "dev": true }, "handlebars": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", - "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.2.tgz", + "integrity": "sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -3363,9 +3363,9 @@ "dev": true }, "uglify-js": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz", - "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", + "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", "dev": true, "optional": true, "requires": { @@ -5500,6 +5500,11 @@ "safe-buffer": "^5.0.1" } }, + "simplex-noise": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-2.4.0.tgz", + "integrity": "sha512-OjyDWm/QZjVbMrPxDVi9b2as+SeNn9EBXlrWVRlFW+TSyWMSXouDryXkQN0vf5YP+QZKobrmkvx1eQYPLtuqfw==" + }, "since-app-start": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/since-app-start/-/since-app-start-0.3.3.tgz", diff --git a/package.json b/package.json index 460f5a3..9f1e6c8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "version": "1.0.0", "dependencies": { "p5": "^0.10.2", + "simplex-noise": "^2.4.0", "voronoi": "^1.0.0" }, "devDependencies": { @@ -23,7 +24,7 @@ "copycat-brunch": "^1.1.1", "digest-brunch": "^1.6.0", "git-directory-deploy": "^1.5.1", - "handlebars": "^4.5.1", + "handlebars": "^4.5.2", "html-brunch-static": "^1.4.1", "uglify-js-brunch": "^2.10.0" }