mirror of
https://github.com/tuna/mirror-web.git
synced 2025-12-25 20:32:46 +00:00
WebGL optimization
This commit is contained in:
parent
bba165a7e6
commit
92ce69c076
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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<any> {
|
||||
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<Element, string[]> = 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue