import p5 from "p5"; import io from "socket.io-client"; import UPNG from "upng-js"; const socket = io("http://localhost:3000"); const params = { fps: 60, zoom: 0.01, backgroundTicks: 60, textSpread: 0, maxSpawns: 1, textAlpha: 0.0, textTicks: 1, }; socket.on("paramUpdate", ({ key, value }) => { params[key] = value; console.log(`Updated param ${key} to ${value}`); }); const poem = "everybody is pretty ugly \ lover \ you are my mirror \ I can't judge this \ predicament \ autism alienation and sitting beach \ this depression's a symptom but it's disease too \ spaces \ interiors in city reach peach intention lover drag sleepless depth fear dirt lone"; const realText = poem.split(" ").filter((t) => t.length > 0); const sketch = (p) => { const imageW = 20384, imageH = 28064; const tileW = 8192; const tileH = 8192; const tileCols = Math.ceil(imageW / tileW); const tileRows = Math.ceil(imageH / tileH); const tileCount = tileCols * tileRows; let gl; let texArray; let shaderProgram; p.setup = async () => { p.createCanvas(p.windowWidth, p.windowHeight, p.WEBGL); // Check for correct webgl version gl = p._renderer.GL; if (!(gl instanceof WebGL2RenderingContext)) { console.error("WebGL2 required!"); } else { console.log("WebGL2 supported!"); } // Create texture array texArray = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D_ARRAY, texArray); // Allocate texture storage gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.R8, tileW, tileH, tileCount); const promises = []; let n_downloaded = 0; for (let row = 0; row < tileRows; row++) { for (let column = 0; column < tileCols; column++) { const x = column * tileW; const y = row * tileH; const w = Math.min(tileW, imageW - x); const h = Math.min(tileH, imageH - y); promises.push( fetch(`assets/img/tiles/tile_x${x}_y${y}_w${w}_h${h}.png`) .then((res) => res.arrayBuffer()) .then((buffer) => { const img = UPNG.decode(buffer); console.log( `img.data.length: ${img.data.length}, img.width: ${img.width}, img.height: ${img.height}`, ); const i = row * tileCols + column; gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, i, img.width, img.height, 1, gl.RED, gl.UNSIGNED_BYTE, img.data, ); n_downloaded++; console.log( `Downloaded ${n_downloaded} / ${tileCount} (${Math.round((n_downloaded / tileCount) * 100)}%) tiles`, ); }), ); } } await Promise.all(promises); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); shaderProgram = await p.loadShader( "shader/shader.vert", "shader/shader.frag", ); console.log(shaderProgram); // const shaderFile = await fetch("shader/sobel.frag"); // const shaderText = await shaderFile.text(); // sobel = p.createFilterShader(shaderText); // const stream = c.elt.captureStream(60); }; let bgTick = 0; let textTick = 0; let sx, sy; let cw, ch; let word, bounds; p.draw = () => { p.background(220); // update scale p.setFrameRate(params.fps); bgTick %= params.backgroundTicks; // current width/height if (bgTick++ == 0) { cw = Math.floor(imageW * params.zoom); ch = Math.floor(imageH * params.zoom); // random starting position // subtract cw/ch to avoid going out of bounds sx = Math.random(0) * (imageW - cw); sy = Math.random(0) * (imageH - ch); } textTick %= params.textTicks; if (textTick++ == 0) { (word, (bounds = nextText())); } drawTiles(sx, sy, cw, ch); if (word && bounds) drawText(word, bounds); }; function nextText() { for (let i = 0; i < Math.random() * params.maxSpawns; i++) { const word = realText[Math.floor(Math.random() * realText.length)]; const wx = p.windowWidth / 2 - p.textWidth(word) / 2 + p.random(-params.textSpread, params.textSpread); const wy = p.windowHeight / 2 - p.textAscent() / 2 + p.random(-params.textSpread, params.textSpread); const bounds = p.textBounds(word, wx, wy); return { word, bounds }; } } function drawText(word, bounds) { const whitespace = 2; p.stroke(0, 0, 0, params.textAlpha); p.fill(255, 255, 255, params.textAlpha); p.rect( bounds.x - whitespace, bounds.y - whitespace, bounds.w + 2 * whitespace, bounds.h + 2 * whitespace, ); p.noStroke(); p.fill(0, 0, 0, params.textAlpha); p.text(word, bounds.x, bounds.y + p.textAscent()); } function drawTiles(x, y, width, height) { // clear screen p.background(0); p.shader(shaderProgram); // p.ortho(x, x + width, y + height, y); // p.ortho(0, -p.windowWidth, -p.windowHeight, 0); console.log( `ortho: ${-p.width / 2}, ${p.width / 2}, ${-p.height / 2}, ${p.height / 2}`, ); p.ortho(0, p.width, -p.height, 0); // Bind texture array gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D_ARRAY, texArray); for ( let row = Math.floor(y / tileW); row <= Math.floor((y + height) / tileW); row++ ) { for ( let col = Math.floor(x / tileW); col <= Math.floor((x + width) / tileW); col++ ) { const currentLayer = row * tileCols + col; // Tell shader which layer to sample shaderProgram.setUniform("uLayer", currentLayer); // Draw a quad covering the screen console.log( `Drawing tile ${currentLayer} at ${col * tileW}, ${row * tileH}`, ); // p.rect(col * tileW, row * tileH, tileW, tileH); p.rect(0, 0, tileW, tileH); } } } }; new p5(sketch);