diff --git a/static/css/_dark.scss b/static/css/_dark.scss index 6652712..5d6cd10 100644 --- a/static/css/_dark.scss +++ b/static/css/_dark.scss @@ -91,18 +91,24 @@ fill: transparent; } -.darker-text-display { - fill: currentColor; +.darker-canvases > canvas { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + mix-blend-mode: normal; } -.darker-canvas { +.darker-canvases { position: fixed; top: 0; left: 0; height: 100vh; width: 100vw; - z-index: 100; + z-index: 100000; pointer-events: none; + mix-blend-mode: multiply; } .label-status { @@ -120,10 +126,6 @@ pointer-events: none; } -.darker-text > span { - color: transparent; -} - @font-face { font-family: 'Source Han Sans SC'; src: url('../fonts/sss-regular.otf') format('otf'), diff --git a/static/js/dark.js b/static/js/dark.js index d53d811..6b41cce 100644 --- a/static/js/dark.js +++ b/static/js/dark.js @@ -36,6 +36,10 @@ var __generator = (this && this.__generator) || function (thisArg, body) { }; console.log("喵呜喵呜喵"); var modes = ["light", "dark", "darker", "lighter"]; +var vertShaderSrc = "\nattribute vec4 a_pos;\nuniform vec2 u_screen;\nuniform vec2 u_mouse;\nuniform vec2 u_offset;\n\nvarying float v_opacity;\n\nvoid main() {\n vec2 pos_screen = a_pos.xy + u_offset;\n vec2 pos_translated;\n vec2 diff = pos_screen - u_mouse;\n pos_translated.x = pos_screen.x + diff.x * a_pos.z;\n pos_translated.y = pos_screen.y + diff.y * a_pos.z;\n gl_Position.x = pos_translated.x / u_screen.x * 2.0 - 1.0;\n gl_Position.y = - (pos_translated.y / u_screen.y * 2.0 - 1.0);\n gl_Position.z = 0.0;\n gl_Position.w = 1.0;\n\n float dist = sqrt(diff.x * diff.x + diff.y * diff.y);\n // 30 - 50px\n v_opacity = clamp((dist - 30.0) / 20.0, 0.0, 1.0);\n}\n"; +var fragShaderSrc = "\nprecision mediump float;\nvarying float v_opacity;\n\nvoid main() {\n gl_FragColor = vec4(0.0, 0.0, 0.0, v_opacity);\n}\n"; +var radialVertShaderSrc = "\n\n"; +var radialFragShaderSrc = "\n"; function loadFont(fn) { return __awaiter(this, void 0, void 0, function () { var url, req, resp; @@ -154,8 +158,11 @@ function discretize(path) { } function applyMode(m) { if (m === 'darker') { + ensureCanvas(); rescanAt(document.body); - ensureDarker(); + // TODO: async reassemble + reassemble(); + ensureObs(); renderLoop(); } } @@ -179,6 +186,7 @@ function rescan(mutations, obs) { var n = _b[_a]; rescanAt(n); } + reassemble(); } } // TODO: allow scaning arbitrary HTML-side nodes @@ -220,7 +228,7 @@ function rescanAt(el) { var fs = styles.fontSize; var fsNum = parseFloat(fs.match(/^[0-9.]+/)[0]); var isBold = styles.fontWeight !== '400'; - var _loop_2 = function () { + while (inner !== '') { // Trim empty stuff var startTrim = inner.length - inner.trimStart().length; if (startTrim != 0) { @@ -234,39 +242,36 @@ function rescanAt(el) { else { var first = inner.substring(0, 1); inner = inner.substring(1); - var node_1 = document.createElement('span'); - node_1.classList.add('darker-text'); + var node = document.createElement('span'); + node.classList.add('darker-text'); var holder = document.createElement('span'); holder.innerText = first; - node_1.appendChild(holder); - wrapper.appendChild(node_1); + node.appendChild(holder); + wrapper.appendChild(node); // console.log(fsNum); - var glyph_1 = resolveGlyph(first, fsNum, isBold); - if (!glyph_1) - return "continue"; - node_1.classList.add('darker-traced'); - node_1.setAttribute('data-glyph', glyph_1); + var glyph = resolveGlyph(first, fsNum, isBold); + if (!glyph) + continue; + node.classList.add('darker-traced'); + node.setAttribute('data-glyph', glyph); // console.log(first, glyph); // Debug // TODO: drop me - setTimeout(function () { - var _a = node_1.getBoundingClientRect(), width = _a.width, height = _a.height; - var svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); - svg.setAttribute('viewbox', "0 0 ".concat(width, " ").concat(height)); - svg.style.width = width + 'px'; - svg.style.height = height + 'px'; - svg.classList.add('darker-text-render'); - var path = document.createElementNS("http://www.w3.org/2000/svg", 'path'); - path.setAttribute('d', glyph_1); - path.classList.add('darker-processed'); - path.classList.add('darker-text-display'); - svg.appendChild(path); - node_1.appendChild(svg); - }); + // setTimeout(() => { + // const { width, height } = node.getBoundingClientRect(); + // const svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); + // svg.setAttribute('viewbox', `0 0 ${width} ${height}`); + // svg.style.width = width + 'px'; + // svg.style.height = height + 'px'; + // svg.classList.add('darker-text-render'); + // const path = document.createElementNS("http://www.w3.org/2000/svg", 'path'); + // path.setAttribute('d', glyph); + // path.classList.add('darker-processed'); + // path.classList.add('darker-text-display'); + // svg.appendChild(path); + // node.appendChild(svg); + // }); } - }; - while (inner !== '') { - _loop_2(); } el.replaceChild(wrapper, child); } @@ -377,7 +382,63 @@ function rescanSVG(el) { } var obs = null; var canvas = null; -function ensureDarker() { +var backdrop = null; +var overlay = null; +var shaderCtx = { + a_pos: null, + u_screen_loc: null, + u_mouse_loc: null, + u_offset_loc: null, + triangleCnt: 0, + gl: null, +}; +function ensureCanvas() { + var container = document.createElement('div'); + container.classList.add('darker-canvases'); + if (backdrop === null) { + backdrop = document.createElement('canvas'); + container.appendChild(backdrop); + } + if (canvas === null) { + canvas = document.createElement('canvas'); + canvas.classList.add('darker-canvas'); + container.appendChild(canvas); + var gl = canvas.getContext('webgl'); + if (!gl) { + alert('WebGL Missing!'); + return; + } + shaderCtx.gl = gl; + var vertShader = gl.createShader(gl.VERTEX_SHADER); + var fragShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(vertShader, vertShaderSrc); + gl.shaderSource(fragShader, fragShaderSrc); + gl.compileShader(vertShader); + console.log(gl.getShaderInfoLog(vertShader)); + gl.compileShader(fragShader); + console.log(gl.getShaderInfoLog(fragShader)); + // TODO: check compile status + var prog = gl.createProgram(); + gl.attachShader(prog, vertShader); + gl.attachShader(prog, fragShader); + gl.linkProgram(prog); + var a_pos_loc = gl.getAttribLocation(prog, 'a_pos'); + shaderCtx.a_pos = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, shaderCtx.a_pos); + gl.useProgram(prog); + gl.enableVertexAttribArray(a_pos_loc); + gl.vertexAttribPointer(a_pos_loc, 3, gl.FLOAT, false, 0, 0); + shaderCtx.u_screen_loc = gl.getUniformLocation(prog, 'u_screen'); + shaderCtx.u_mouse_loc = gl.getUniformLocation(prog, 'u_mouse'); + shaderCtx.u_offset_loc = gl.getUniformLocation(prog, 'u_offset'); + } + if (overlay === null) { + overlay = document.createElement('canvas'); + container.appendChild(overlay); + } + document.body.appendChild(container); +} +function ensureObs() { if (obs === null) { obs = new MutationObserver(rescan); obs.observe(document.body, { @@ -385,105 +446,32 @@ function ensureDarker() { subtree: true, }); } - if (canvas === null) { - canvas = document.createElement('canvas'); - canvas.classList.add('darker-canvas'); - document.body.appendChild(canvas); - } } var textCache = new WeakMap(); -var renderStopped = false; -function renderLoop() { - if (renderStopped) - return; - if (canvas === null) - return; - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - var ctx = canvas.getContext('2d'); +function reassemble() { var traced = document.getElementsByClassName('darker-traced'); - var cnt = 0; - function tracePaths(paths, sx, sy, width, height, scale) { - var _loop_3 = function (path) { + var assembledBuffer = []; + var trig = 0; + function assemblePath(paths, sx, sy, scale) { + for (var _i = 0, paths_3 = paths; _i < paths_3.length; _i++) { + var path = paths_3[_i]; var dpath = discretize(path); if (dpath.length === 1) - return "continue"; - ctx.beginPath(); - var lastAng = null; - var incRegionStart = null; - function commit(x, y) { - if (incRegionStart === null) - return; - var color = "black"; - // if(x < sx || x > sx + width || y < sy || y > sy + height) { - // color = 'red'; - // } - var bx = incRegionStart.x, by = incRegionStart.y; - // if(bx < sx || bx > sx + width || by < sy || by > sy + height) { - // color = 'green'; - // } - ctx.fillStyle = "".concat(color); - ctx.beginPath(); - ctx.moveTo(bx, by); - ctx.lineTo((bx - mx) * 1000 + bx, (by - my) * 1000 + by); - ctx.lineTo((x - mx) * 1000 + x, (y - my) * 1000 + y); - ctx.lineTo(x, y); - ctx.fill(); - cnt += 2; - lastAng = null; - incRegionStart = null; - } + continue; for (var i = 0; i < dpath.length; ++i) { var cx = dpath[i].x * scale + sx; var cy = dpath[i].y * scale + sy; var nx = dpath[(i + 1) % dpath.length].x * scale + sx; var ny = dpath[(i + 1) % dpath.length].y * scale + sy; - // Test angle. This is a counter-clockwise loop (in canonical axis orientation) - var segAng = Math.atan2(nx - cx, ny - cy); - var rayAng = Math.atan2(cx - mx, cy - my); - var diffAng = segAng - rayAng; - if (diffAng > Math.PI) - diffAng -= Math.PI * 2; - if (diffAng < -Math.PI) - diffAng += Math.PI * 2; - if (diffAng < 0 || diffAng > Math.PI) { - commit(cx, cy); - // TODO: commit - continue; - } - // Check for continous increasing region - if (lastAng === null) { - lastAng = segAng; - incRegionStart = { x: cx, y: cy }; - continue; - } - var diffLastAng = segAng - rayAng; - if (diffLastAng > Math.PI) - diffLastAng -= Math.PI * 2; - if (diffLastAng < -Math.PI) - diffLastAng += Math.PI * 2; - if (diffLastAng > 0) { - lastAng = segAng; - continue; - } - else { - commit(cx, cy); - lastAng = segAng; - continue; - } + // Expand a little bit + assembledBuffer.push(cx, cy, 0, nx, ny, 0, cx, cy, 100, cx, cy, 100, nx, ny, 100, nx, ny, 0); + trig += 2; } - var bx = dpath[0].x * scale + sx; - var by = dpath[0].y * scale + sy; - commit(bx, by); - }; - for (var _i = 0, paths_3 = paths; _i < paths_3.length; _i++) { - var path = paths_3[_i]; - _loop_3(path); } } for (var _i = 0, traced_1 = traced; _i < traced_1.length; _i++) { var trace = traced_1[_i]; - var _a = trace.getBoundingClientRect(), sx = _a.x, sy = _a.y, width = _a.width, height = _a.height; + var _a = trace.getBoundingClientRect(), x = _a.x, y = _a.y, width = _a.width, height = _a.height; var scale = 1; if (trace.tagName === 'use') { var sym = document.getElementById(trace.getAttribute('xlink:href').substring(1)); @@ -499,7 +487,7 @@ function renderLoop() { console.warn("Symbol not in cache: ".concat(sym.id)); continue; } - tracePaths(paths, sx, sy, width, height, scale_1); + assemblePath(paths, x, y + window.scrollY, scale_1); } else if (trace.tagName === 'svg') { var paths = svgCache[trace.id]; @@ -507,7 +495,7 @@ function renderLoop() { console.warn("SVG not in cache: ".concat(trace.id)); continue; } - tracePaths(paths, sx, sy, width, height, 1); + assemblePath(paths, x, y + window.scrollY, scale); } else if (trace.classList.contains('darker-text')) { var cached = textCache.get(trace); @@ -517,10 +505,54 @@ function renderLoop() { cached = paths.map(function (e) { return e.getAttribute('d'); }); textCache.set(trace, cached); } - tracePaths(cached, sx, sy, width, height, 1); + assemblePath(cached, x, y + window.scrollY, scale); } } - console.log(cnt); + // TODO: error on me + if (!shaderCtx.gl) + return; + shaderCtx.gl.bufferData(shaderCtx.gl.ARRAY_BUFFER, new Float32Array(assembledBuffer), shaderCtx.gl.STATIC_DRAW); + shaderCtx.triangleCnt = trig * 3; +} +var renderStopped = false; +function renderLoop() { + if (!canvas || !backdrop || !overlay || !shaderCtx.gl) + return; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + { + backdrop.width = window.innerWidth; + backdrop.height = window.innerHeight; + var ctx = backdrop.getContext('2d'); + var grad = ctx.createRadialGradient(mx, my, 100, mx, my, 600); + grad.addColorStop(0, "#333"); + grad.addColorStop(1, "#111"); + ctx.fillStyle = grad; + ctx.fillRect(0, 0, backdrop.width, backdrop.height); + } + var gl = shaderCtx.gl; + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2fv(shaderCtx.u_screen_loc, [window.innerWidth, window.innerHeight]); + gl.uniform2fv(shaderCtx.u_mouse_loc, [mx, my]); + gl.uniform2fv(shaderCtx.u_offset_loc, [0, -window.scrollY]); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.enable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + // gl.enable(gl.SAMPLE_COVERAGE); + // gl.sampleCoverage(0.5, false); + gl.drawArrays(gl.TRIANGLES, 0, shaderCtx.triangleCnt); + { + overlay.width = window.innerWidth; + overlay.height = window.innerHeight; + var ctx = overlay.getContext('2d'); + var grad = ctx.createRadialGradient(mx, my, 40, mx, my, 60); + grad.addColorStop(0, "rgba(255,255,255,0.4)"); + grad.addColorStop(1, "rgba(255,255,255,0)"); + ctx.fillStyle = grad; + ctx.fillRect(0, 0, overlay.width, overlay.height); + } requestAnimationFrame(renderLoop); } document.addEventListener('DOMContentLoaded', function () { diff --git a/static/js/dark.ts b/static/js/dark.ts index 96fc3d1..366f495 100644 --- a/static/js/dark.ts +++ b/static/js/dark.ts @@ -5,6 +5,47 @@ type Mode = typeof modes[number]; type DiscreteLoop = { x: number, y: number }[]; +const vertShaderSrc = ` +attribute vec4 a_pos; +uniform vec2 u_screen; +uniform vec2 u_mouse; +uniform vec2 u_offset; + +varying float v_opacity; + +void main() { + vec2 pos_screen = a_pos.xy + u_offset; + vec2 pos_translated; + vec2 diff = pos_screen - u_mouse; + pos_translated.x = pos_screen.x + diff.x * a_pos.z; + pos_translated.y = pos_screen.y + diff.y * a_pos.z; + gl_Position.x = pos_translated.x / u_screen.x * 2.0 - 1.0; + gl_Position.y = - (pos_translated.y / u_screen.y * 2.0 - 1.0); + gl_Position.z = 0.0; + gl_Position.w = 1.0; + + float dist = sqrt(diff.x * diff.x + diff.y * diff.y); + // 30 - 50px + v_opacity = clamp((dist - 30.0) / 20.0, 0.0, 1.0); +} +`; + +const fragShaderSrc = ` +precision mediump float; +varying float v_opacity; + +void main() { + gl_FragColor = vec4(0.0, 0.0, 0.0, v_opacity); +} +`; + +const radialVertShaderSrc = ` + +` + +const radialFragShaderSrc = ` +` + async function loadFont(fn: string): Promise { const url = `/static/fonts/${fn}`; const req = await fetch(url); @@ -108,8 +149,11 @@ function discretize(path: string): DiscreteLoop { function applyMode(m: Mode) { if(m === 'darker') { + ensureCanvas(); rescanAt(document.body); - ensureDarker(); + // TODO: async reassemble + reassemble(); + ensureObs(); renderLoop(); } } @@ -131,6 +175,7 @@ function rescan(mutations: MutationRecord[], obs: MutationObserver) { } for(const n of m.addedNodes) rescanAt(n as HTMLElement); + reassemble(); } } @@ -221,22 +266,22 @@ function rescanAt(el: HTMLElement) { // Debug // TODO: drop me - setTimeout(() => { - const { width, height } = node.getBoundingClientRect(); - const svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); - svg.setAttribute('viewbox', `0 0 ${width} ${height}`); - svg.style.width = width + 'px'; - svg.style.height = height + 'px'; - svg.classList.add('darker-text-render'); + // setTimeout(() => { + // const { width, height } = node.getBoundingClientRect(); + // const svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); + // svg.setAttribute('viewbox', `0 0 ${width} ${height}`); + // svg.style.width = width + 'px'; + // svg.style.height = height + 'px'; + // svg.classList.add('darker-text-render'); - const path = document.createElementNS("http://www.w3.org/2000/svg", 'path'); - path.setAttribute('d', glyph); - path.classList.add('darker-processed'); - path.classList.add('darker-text-display'); - svg.appendChild(path); + // const path = document.createElementNS("http://www.w3.org/2000/svg", 'path'); + // path.setAttribute('d', glyph); + // path.classList.add('darker-processed'); + // path.classList.add('darker-text-display'); + // svg.appendChild(path); - node.appendChild(svg); - }); + // node.appendChild(svg); + // }); } } @@ -341,7 +386,76 @@ function rescanSVG(el: SVGElement) { let obs: MutationObserver | null = null; let canvas: HTMLCanvasElement | null = null; -function ensureDarker() { +let backdrop: HTMLCanvasElement | null = null; +let overlay: HTMLCanvasElement | null = null; +const shaderCtx = { + a_pos: null as WebGLBuffer | null, + + u_screen_loc: null as WebGLUniformLocation | null, + u_mouse_loc: null as WebGLUniformLocation | null, + u_offset_loc: null as WebGLUniformLocation | null, + + triangleCnt: 0, + gl: null as WebGLRenderingContext | null, +}; + +function ensureCanvas() { + const container = document.createElement('div'); + container.classList.add('darker-canvases'); + + if(backdrop === null) { + backdrop = document.createElement('canvas'); + container.appendChild(backdrop); + } + if(canvas === null) { + canvas = document.createElement('canvas'); + canvas.classList.add('darker-canvas'); + container.appendChild(canvas); + + const gl = canvas.getContext('webgl'); + if(!gl) { + alert('WebGL Missing!'); + return; + } + + shaderCtx.gl = gl; + + const vertShader = gl.createShader(gl.VERTEX_SHADER)!; + const fragShader = gl.createShader(gl.FRAGMENT_SHADER)!; + gl.shaderSource(vertShader, vertShaderSrc); + gl.shaderSource(fragShader, fragShaderSrc); + gl.compileShader(vertShader); + console.log(gl.getShaderInfoLog(vertShader)); + gl.compileShader(fragShader); + console.log(gl.getShaderInfoLog(fragShader)); + // TODO: check compile status + + const prog = gl.createProgram()!; + gl.attachShader(prog, vertShader); + gl.attachShader(prog, fragShader); + gl.linkProgram(prog); + + const a_pos_loc = gl.getAttribLocation(prog, 'a_pos'); + shaderCtx.a_pos = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, shaderCtx.a_pos); + + gl.useProgram(prog); + gl.enableVertexAttribArray(a_pos_loc); + gl.vertexAttribPointer(a_pos_loc, 3, gl.FLOAT, false, 0, 0); + + shaderCtx.u_screen_loc = gl.getUniformLocation(prog, 'u_screen'); + shaderCtx.u_mouse_loc = gl.getUniformLocation(prog, 'u_mouse'); + shaderCtx.u_offset_loc = gl.getUniformLocation(prog, 'u_offset'); + } + if(overlay === null) { + overlay = document.createElement('canvas'); + container.appendChild(overlay); + } + + document.body.appendChild(container); +} + +function ensureObs() { if(obs === null) { obs = new MutationObserver(rescan) obs.observe(document.body, { @@ -349,113 +463,45 @@ function ensureDarker() { subtree: true, }); } - - if(canvas === null) { - canvas = document.createElement('canvas'); - canvas.classList.add('darker-canvas'); - document.body.appendChild(canvas); - } } const textCache: WeakMap = new WeakMap(); -let renderStopped = false; -function renderLoop() { - if(renderStopped) return; - if(canvas === null) return; - - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - const ctx = canvas.getContext('2d')!; - +function reassemble() { const traced = document.getElementsByClassName('darker-traced'); - let cnt = 0; - function tracePaths(paths: string[], sx: number, sy: number, width: number, height: number, scale: number) { + const assembledBuffer: number[] = []; + let trig = 0; + function assemblePath(paths: string[], sx: number, sy: number, scale: number) { for(const path of paths) { const dpath = discretize(path); if(dpath.length === 1) continue; - ctx.beginPath(); - - let lastAng: number | null = null; - let incRegionStart: { x: number, y: number } | null = null; - - function commit(x: number, y: number) { - if(incRegionStart === null) return; - let color = `black`; - // if(x < sx || x > sx + width || y < sy || y > sy + height) { - // color = 'red'; - // } - - const { x: bx, y: by } = incRegionStart; - - // if(bx < sx || bx > sx + width || by < sy || by > sy + height) { - // color = 'green'; - // } - - ctx.fillStyle = `${color}`; - - ctx.beginPath(); - ctx.moveTo(bx, by); - ctx.lineTo((bx - mx) * 1000 + bx, (by - my) * 1000 + by); - ctx.lineTo((x - mx) * 1000 + x, (y - my) * 1000 + y); - ctx.lineTo(x, y); - ctx.fill(); - - cnt += 2; - - lastAng = null; - incRegionStart = null; - } for(let i = 0; i < dpath.length; ++i) { - const cx = dpath[i].x * scale + sx; - const cy = dpath[i].y * scale + sy; + let cx = dpath[i].x * scale + sx; + let cy = dpath[i].y * scale + sy; - const nx = dpath[(i + 1) % dpath.length].x * scale + sx; - const ny = dpath[(i + 1) % dpath.length].y * scale + sy; + let nx = dpath[(i + 1) % dpath.length].x * scale + sx; + let ny = dpath[(i + 1) % dpath.length].y * scale + sy; - // Test angle. This is a counter-clockwise loop (in canonical axis orientation) - const segAng = Math.atan2(nx - cx, ny - cy); - const rayAng = Math.atan2(cx - mx, cy - my); + // Expand a little bit + assembledBuffer.push( + cx, cy, 0, + nx, ny, 0, + cx, cy, 100, - let diffAng = segAng - rayAng; - if(diffAng > Math.PI) diffAng -= Math.PI * 2; - if(diffAng < -Math.PI) diffAng += Math.PI * 2; - if(diffAng < 0 || diffAng > Math.PI) { - commit(cx, cy); - // TODO: commit - continue; - } - - // Check for continous increasing region - if(lastAng === null) { - lastAng = segAng; - incRegionStart = { x: cx, y: cy }; - continue; - } - - let diffLastAng = segAng - rayAng; - if(diffLastAng > Math.PI) diffLastAng -= Math.PI * 2; - if(diffLastAng < -Math.PI) diffLastAng += Math.PI * 2; - if(diffLastAng > 0) { - lastAng = segAng; - continue; - } else { - commit(cx, cy); - lastAng = segAng; - continue; - } + cx, cy, 100, + nx, ny, 100, + nx, ny, 0, + ); + trig += 2; } - - const bx = dpath[0].x * scale + sx; - const by = dpath[0].y * scale + sy; - commit(bx, by); } } for(const trace of traced) { - const { x: sx, y: sy, width, height } = trace.getBoundingClientRect(); + const { x, y, width, height } = trace.getBoundingClientRect(); + let scale = 1; if(trace.tagName === 'use') { @@ -474,14 +520,14 @@ function renderLoop() { continue; } - tracePaths(paths, sx, sy, width, height, scale) + assemblePath(paths, x, y + window.scrollY, scale); } else if(trace.tagName === 'svg') { const paths: string[] | undefined = svgCache[trace.id]; if(paths === undefined) { console.warn(`SVG not in cache: ${trace.id}`); continue; } - tracePaths(paths, sx, sy, width, height, 1); + assemblePath(paths, x, y + window.scrollY, scale); } else if(trace.classList.contains('darker-text')) { let cached = textCache.get(trace); if(!cached) { @@ -490,11 +536,61 @@ function renderLoop() { cached = paths.map(e => e.getAttribute('d')!); textCache.set(trace, cached); } - tracePaths(cached, sx, sy, width, height, 1); + assemblePath(cached, x, y + window.scrollY, scale); } } - console.log(cnt); + // TODO: error on me + if(!shaderCtx.gl) return; + + shaderCtx.gl.bufferData(shaderCtx.gl.ARRAY_BUFFER, new Float32Array(assembledBuffer), shaderCtx.gl.STATIC_DRAW); + shaderCtx.triangleCnt = trig * 3; +} + +let renderStopped = false; +function renderLoop() { + if(!canvas || !backdrop || !overlay || !shaderCtx.gl) return; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + { + backdrop.width = window.innerWidth; + backdrop.height = window.innerHeight; + const ctx = backdrop.getContext('2d')!; + const grad = ctx.createRadialGradient(mx, my, 100, mx, my, 600); + grad.addColorStop(0, "#333"); + grad.addColorStop(1, "#111"); + ctx.fillStyle = grad; + ctx.fillRect(0, 0, backdrop.width, backdrop.height); + } + + const gl = shaderCtx.gl; + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.uniform2fv(shaderCtx.u_screen_loc, [window.innerWidth, window.innerHeight]); + gl.uniform2fv(shaderCtx.u_mouse_loc, [mx, my]); + gl.uniform2fv(shaderCtx.u_offset_loc, [0, -window.scrollY]); + + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.enable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + // gl.enable(gl.SAMPLE_COVERAGE); + // gl.sampleCoverage(0.5, false); + + gl.drawArrays(gl.TRIANGLES, 0, shaderCtx.triangleCnt); + + { + overlay.width = window.innerWidth; + overlay.height = window.innerHeight; + const ctx = overlay.getContext('2d')!; + const grad = ctx.createRadialGradient(mx, my, 40, mx, my, 60); + grad.addColorStop(0, "rgba(255,255,255,0.4)"); + grad.addColorStop(1, "rgba(255,255,255,0)"); + ctx.fillStyle = grad; + ctx.fillRect(0, 0, overlay.width, overlay.height); + } requestAnimationFrame(renderLoop); } diff --git a/static/js/vertex.glsl b/static/js/vertex.glsl new file mode 100644 index 0000000..a4adfeb --- /dev/null +++ b/static/js/vertex.glsl @@ -0,0 +1,13 @@ +attribute vec3 a_pos; +uniform vec2 u_screen; +uniform vec2 u_mouse; +uniform vec2 u_offset; + +void main() { + vec2 pos_screen = a_pos.xy - u_offset; + vec2 pos_translated; + pos_translated.x = pos_screen.x + (pos_screen.x - u_mouse.x) * a_pos.z * 1000; + pos_translated.y = pos_screen.y + (pos_screen.y - u_mouse.y) * a_pos.z * 1000; + gl_Position.x = pos_translated.x / u_screen * 2 - 1; + gl_Position.y = pos_translated.y / u_screen * 2 - 1; +} \ No newline at end of file