diff --git a/app/assets/4/index.html b/app/assets/4/index.html new file mode 120000 index 0000000..9840de0 --- /dev/null +++ b/app/assets/4/index.html @@ -0,0 +1 @@ +../../sketch-template.html \ No newline at end of file diff --git a/app/assets/index.html b/app/assets/index.html index 745cbc3..836e49c 100644 --- a/app/assets/index.html +++ b/app/assets/index.html @@ -9,11 +9,12 @@

P5.js generative art

-
    -
  1. gradient burst
  2. -
  3. gradient jungle
  4. -
  5. peanut butter and jelly
  6. -
+
diff --git a/app/assets/shaders/displacement.frag b/app/assets/shaders/displacement.frag new file mode 100644 index 0000000..4818799 --- /dev/null +++ b/app/assets/shaders/displacement.frag @@ -0,0 +1,37 @@ +precision mediump float; + +// lets grab texcoords just for fun +varying vec2 vTexCoord; + +// our texture and image coming from p5 +uniform sampler2D u_src; +uniform sampler2D u_map; + +// how much to displace by (controlled by mouse) +uniform float u_intensity; + +void main() { + + vec2 uv = vTexCoord; + // the texture is loaded upside down and backwards by default so lets flip it + uv = 1.0 - uv; + + // get the displacement map as a vec4 using texture2D + vec4 mapTex = texture2D(u_map, uv); + + // lets get the average color of the rgb values + float avg = dot(mapTex.rgb, vec3(0.33333)); + + // then spread it between -1 and 1 + avg = avg * 2.0 - 1.0; + + // we will displace the image by the average color times the amt of displacement + float disp = avg * u_intensity; + + // displacement works by moving the texture coordinates of one image with the colors of another image + // add the displacement to the texture coordinages + vec4 srcTex = texture2D(u_src, uv + disp); + + // output the image + gl_FragColor = srcTex; +} diff --git a/app/sketches/4.js b/app/sketches/4.js new file mode 100644 index 0000000..ed0dca5 --- /dev/null +++ b/app/sketches/4.js @@ -0,0 +1,278 @@ +new p5(sketch => { + sketch.disableFriendlyErrors = false; + // reused dimensions and a seed + let seed, width, height, noiseResolution, overdraw, blurQuality; + const layers = {}; // offscreen layers + const shaders = {}; // shaders + const lib = {}; // libraries + + sketch.preload = () => { + shaders.whiteNoise = sketch.loadShader( + '../shaders/base.vert', + '../shaders/white-noise.frag' + ); + shaders.displacement = sketch.loadShader( + '../shaders/base.vert', + '../shaders/displacement.frag' + ); + shaders.blurH = sketch.loadShader( + '../shaders/base.vert', + '../shaders/blur-two-pass.frag' + ); + shaders.blurV = sketch.loadShader( + '../shaders/base.vert', + '../shaders/blur-two-pass.frag' + ); + lib.voronoi = new Voronoi() + } + + sketch.setup = () => { + filenamePrefix = 'seigler-p5-3-lenses'; + 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 = 1; + + 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.buffer = sketch.createGraphics(width, height); + layers.buffer.colorMode(sketch.HSL, 1); + + layers.cells = sketch.createGraphics(width, height); + layers.cells.colorMode(sketch.HSL, 1); + layers.cells.noStroke(); + + layers.noise = sketch.createGraphics(width, height, sketch.WEBGL); + + layers.displacement = 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 { + let seed = Math.floor(sketch.random(1000000000000)); + window.location.hash = seed; + sketch.randomSeed(seed); + } + + sketch.noiseSeed(sketch.random(0, 1000000000)); + sketch.blendMode(sketch.BLEND); + // sketch.background(0.5); + + let stripeAngle = sketch.random(0, Math.PI); + let stripeWidth = Math.min(width, height) / sketch.random(10, 80); + let stripeLength = Math.max(width, height) * Math.SQRT2; + let numStripes = Math.ceil(Math.SQRT2 * stripeLength / stripeWidth); + let stripeHue = sketch.random(); + let ox = width/2, oy = height/2; + let dx = Math.cos(stripeAngle) * stripeLength / 2; + let dy = Math.sin(stripeAngle) * stripeLength / 2; + layers.buffer.strokeCap(sketch.SQUARE); + for (let i = Math.ceil(numStripes / 2); i > 0; i--) { + layers.buffer.stroke(stripeHue, 0.5, 0.2 + 0.1 * (i % 2)); + layers.buffer.strokeWeight((i * 2 - 1) * stripeWidth); + layers.buffer.line(ox - dx, oy - dy, ox + dx, oy + dy); + } + + // square pixels per circle, helps with gridding + sketch.blendMode(sketch.BLEND); + let unit = Math.min(width, height) / Math.round(sketch.random(3, 10)); + let rows = Math.max(1, Math.round(height / unit / Math.sin(Math.PI / 3))) + 1; + let cols = Math.max(1, Math.round(width / unit)) + 1; + let noiseOffset = unit * 200 + Math.SQRT2; + let gridPoints = []; + for (let index = 0; index < rows * cols; index++) { + let col = index % cols; + let row = Math.floor(index / cols); + let noise = noiseResolution.map( + (resolution, noiseIndex) => { + // let gridScale = resolution / Math.min(rows, cols); + return sketch.noise( + noiseOffset * (noiseIndex + 1) + (row - rows / 2) * resolution, + noiseOffset * (noiseIndex + 1) + (col - cols / 2) * resolution + ) + } + ); + gridPoints.push({ + row, + col, + noise, + }); + } + gridPoints.forEach(point => { + let { col, row, noise: [n0, n1, n2, n3] } = point; + point.x = ( + width / (cols - 1) * col + + (row % 2 - 0.5) * unit / 2 + + 1 * n0 * unit * Math.cos(Math.PI * 2 * flattenPerlin(n1)) + ); + point.y = ( + height / (rows - 1) * row + + 1 * n0 * unit * Math.sin(Math.PI * 2 * flattenPerlin(n1)) + ); + }); + let bbox = { + xl: 0 - unit / 2, + xr: width + unit / 2, + yt: 0 - unit / 2, + yb: height + unit / 2 + }; + let diagram = lib.voronoi.compute(gridPoints, bbox); + + // let's draw cells + layers.cells.background(0.5); + diagram.cells.forEach(cell => { + if (cell.halfedges.length >= 3) { + layers.cells.fill(cell.site.noise[2]); + layers.cells.beginShape(); + for(let i = 0; i < cell.halfedges.length + 1; i++) { + const he = cell.halfedges[i % cell.halfedges.length]; + const {x: ax, y: ay} = he.getStartpoint() + const {x: bx, y: by} = he.getEndpoint() + if (i === 0) { + layers.cells.vertex( + sketch.lerp(sketch.lerp(ax, bx, 0.5), cell.site.x, 0.1), + sketch.lerp(sketch.lerp(ay, by, 0.5), cell.site.y, 0.1) + ); + first = false; + } else { + // check angular edge length + layers.cells.quadraticVertex( + sketch.lerp(ax, cell.site.x, 0.1), + sketch.lerp(ay, cell.site.y, 0.1), + sketch.lerp(sketch.lerp(ax, bx, 0.5), cell.site.x, 0.1), + sketch.lerp(sketch.lerp(ay, by, 0.5), cell.site.y, 0.1) + ); + } + } + layers.cells.endShape(); + } + }); + + // blur the cells + let blurSize = unit / 300; + for (let pass = 0; pass < blurQuality; pass++) { + let radius = (blurQuality - pass) * blurSize / blurQuality; + layers.blur1.shader(shaders.blurH); + shaders.blurH.setUniform('tex0', pass == 0 ? layers.cells : layers.blur2); + shaders.blurH.setUniform('texelSize', [radius/width, radius/height]); + shaders.blurH.setUniform('direction', [1.0, 0.0]); + layers.blur1.rect(0, 0, width, height); + layers.blur2.shader(shaders.blurV); + shaders.blurV.setUniform('tex0', layers.blur1); + shaders.blurV.setUniform('texelSize', [radius/width, radius/height]); + shaders.blurV.setUniform('direction', [0.0, 1.0]); + layers.blur2.rect(0, 0, width, height); + } + + layers.cells.image(layers.blur2, 0, 0, width, height); + + layers.displacement.shader(shaders.displacement); + shaders.displacement.setUniform('u_src', layers.buffer); + shaders.displacement.setUniform('u_map', layers.cells); + shaders.displacement.setUniform('u_intensity', 10 / unit); + layers.displacement.rect(0, 0, width, height); + sketch.blendMode(sketch.BLEND); + sketch.image( + layers.displacement, + Math.floor(-width * overdraw / 2), + Math.floor(-height * overdraw / 2) + ); + + 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.blendMode(sketch.OVERLAY); + sketch.image(layers.noise, 0, 0); + + // sketch.stroke(0); + // sketch.strokeWeight(4); + // diagram.vertices.forEach(vertex => { + // sketch.point(vertex.x, vertex.y); + // }); + + // diagram.edges.forEach(({va, vb}) => { + // sketch.line( + // va.x, + // va.y, + // vb.x, + // vb.y + // ); + // }); + + // sketch.stroke('#E00'); + // gridPoints.forEach(({x, y}) => { + // sketch.point(x, y); + // }); + } + + // Fisher-Yates shuffle + function shuffle(array) { + var i = 0, j = 0, temp = null; + + for (i = array.length - 1; i > 0; i -= 1) { + j = Math.floor(sketch.random() * (i + 1)); + temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + } + + // give Perlin noise 0-1 a uniform distribution + function flattenPerlin(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) { + var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2)); + var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2)); + var 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/styles.css b/app/styles.css index 3e2dc6c..9699162 100644 --- a/app/styles.css +++ b/app/styles.css @@ -35,6 +35,11 @@ code { background-color: #222; } +ul { + list-style: none; + padding-left: 0; +} + footer { position: fixed; bottom: 0;