PageSourceSearch

https://iveevi.github.io/tetra.js

js iveevi.github.io collected 2026-10-03 09:21:25 UTC 12,014 bytes, 259 lines download raw bytes

1{
2    const cv = document.getElementById("tetra");
3    const AX = [
4        { name: "Neural graphics", c: [226, 59, 64] },
5        { name: "Real-time rendering", c: [40, 160, 70] },
6        { name: "Programming systems", c: [40, 100, 225] },
7        { name: "Appearance modeling", c: [235, 180, 20] },
8    ];
9    const PAPERS = [
10        { name: "RCGP", title: "RCGP: Resource Contracts for Graphics Programming", href: "rcgp/", w: [0, 0.25, 0.75, 0] },
11        { name: "Glow", title: "Modeling and Rendering Glow Discharge", href: "glow/", w: [0, 0.3, 0, 0.7] },
12        { name: "GFS", title: "Geometry Field Splatting with Gaussian Surfels", href: "https://arxiv.org/abs/2411.17067", w: [0.6, 0.2, 0, 0.2] },
13        { name: "NGF", title: "Neural Geometry Fields for Meshes", href: "ngf/", w: [0.75, 0.25, 0, 0] },
14        { name: "ReSTIR DR", title: "Parameter-space ReSTIR for Differentiable and Inverse Rendering", href: "https://weschang.com/publications/restir-dr/", w: [0, 0.5, 0, 0.5] },
15    ];
16    const r = Math.sqrt(8) / 3;
17    const V = [[0, 1, 0], ...[90, 210, 330].map((d) => [r * Math.cos((d * Math.PI) / 180), -1 / 3, r * Math.sin((d * Math.PI) / 180)])];
18    const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
19    const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
20    const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
21    const F = [[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]].map((f) => {
22        const n = cross(sub(V[f[1]], V[f[0]]), sub(V[f[2]], V[f[0]]));
23        const m = [0, 1, 2].map((k) => (V[f[0]][k] + V[f[1]][k] + V[f[2]][k]) / 3);
24        return dot(n, m) < 0 ? { v: [f[0], f[2], f[1]], n: n.map((x) =>
24 -x) } : { v: f, n };
25    });
26    const E = [[0, 1], [0, 2], [0, 3], [1, 2], [2, 3], [3, 1]];
27    const pos = PAPERS.map((p) => {
28        const t = p.w.reduce((a, b) => a + b, 0);
29        const w = p.w.map((x) => x / t);
30        return {
31            ...p,
32            p: [0, 1, 2].map((k) => w.reduce((s, x, i) => s + x * V[i][k], 0)),
33            c: [0, 1, 2].map((k) => Math.round(w.reduce((s, x, i) => s + x * AX[i].c[k], 0))),
34        };
35    });
36    const still = matchMedia("(prefers-reduced-motion: reduce)");
37    const off = document.createElement("canvas"), octx = off.getContext("2d");
38    const ctx = cv.getContext("2d");
39    let th = 0.5, ph = 0.35, W = 0, H = 0, S = 0, dpr = 1, live = false, drag = null, hover = -1, hoverT = 0, last = 0, shown = [];
40
41    const rot = (p) => {
42        const c = Math.cos(th), s = Math.sin(th), cp = Math.cos(ph), sp = Math.sin(ph);
43        const x = p[0] * c + p[2] * s, z = -p[0] * s + p[2] * c;
44        return [x, p[1] * cp - z * sp, p[1] * sp + z * cp];
45    };
46    const scr = (q) => [W / 2 + q[0] * S, H / 2 - q[1] * S, q[2]];
47
48    function size() {
49        dpr = Math.min(2, devicePixelRatio || 1);
50        W = cv.clientWidth;
51        const B = Math.min(W, 432);
52        H = Math.round(B * 0.5 + 72);
53        cv.style.height = H + "px";
54        cv.width = W * dpr;
55        cv.height = H * dpr;
56        off.width = W;
57        off.height = H;
58        S = B * 0.25;
59    }
60
61    function faces(P, front) {
62        const img = octx.createImageData(W, H), d = img.data;
63        const order = F.map((f, i) => ({ f, i, z: (P[f.v[0]][2] + P[f.v[1]][2] + P[f.v[2]][2]) / 3 })).sort((a, b) => a.z - b.z);
64        for (const { f, i } of order) {
65            const [a, b, c] = f.v.map((k) => P[k]);
66            const A = front[i] ? 0.24 : 0.12;
67            const den = (b[1] - c[1]) * (a[0] - c[0]) + (c[0] - b[0]) * (a[1] - c[1]);
68            const x0 = Math.max(0, Math.floor(Math.min(a[0], b[0], c[0]))), x1 = Math.min(W - 1, Math.ceil(Math.max(a[0], b[0], c[0])));
69            const y0 = Math.max(0, Math.floor(Math.min(a[1], b[1], c[1]))), y1 = Math.min(H - 1, Math.ceil(Math.max(a[1], b[1], c[1])));
70            const ca = AX[f.v[0]].c, cb = AX[f.v[1]].c, cc = AX[f.v[2]].c;
71            for (let y = y0; y <= y1; y++) for (let x = x0; x <= x1; x++) {
72                const px = x + 0.5, py = y + 0.5;
73                const l0 = ((b[1] - c[1]) * (px - c[0]) + (c[0] - b[0]) * (py - c[1])) / den;
74                const l1 = ((c[1] - a[1]) * (px - c[0]) + (a[0] - c[0]) * (py - c[1])) / den;
75                const l2 = 1 - l0 - l1;
76                if (l0 < 0 || l1 < 0 || l2 < 0) continue;
77                const o = (y * W + x) * 4, da = d[o + 3] / 255, na = A + da * (1 - A);
78                for (let k = 0; k < 3; k++) d[o + k] = ((l0 * ca[k] + l1 * cb[k] + l2 * cc[k]) * A + d[o + k] * da * (1 - A)) / na;
79                d[o + 3] = na * 255;
80            }
81        }
82        octx.putImageData(img, 0, 0);
83    }
84
85    function draw() {
86        const P = V.map((v) => scr(rot(v)));
87        const front = F.map((f) => rot(f.n)[2] > 0);
88        faces(P, front);
89        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
90        ctx.clearRect(0, 0, W, H);
91        ctx.drawImage(off, 0, 0, W, H);
92        for (const [i, j] of E) {
93            const vis = F.some((f, k) => front[k] && f.v.includes(i) && f.v.includes(j));
94            ctx.beginPath();
95            ctx.moveTo(P[i][0], P[i][1]);
96            ctx.lineTo(P[j][0], P[j][1]);
97            ctx.setLineDash(vis ? [] : [3, 4]);
98            ctx.strokeStyle = vis ? "rgba(28,27,44,0.55)" : "rgba(28,27,44,0.3)";
99            ctx.lineWidth = 1.2;
100            ctx.stroke();
101        }
102        ctx.setLineDash([]);
103        ctx.font = "700 13px 'Fira Sans Condensed', sans-serif";
104        ctx.textBaseline = "middle";
105        ctx.globalAlpha = hover >= 0 ? 0.3 : 1;
106        AX.forEach((a, i) => {
107            const dx = P[i][0] - W / 2, dy = P[i][1] - H / 2, l = Math.hypot(dx, dy) || 1;
108            const x = P[i][0] + (dx / l) * 16, y = P[i][1] + (dy / l) * 14;
109            ctx.textAlign = Math.abs(dx / l) < 0.35 ? "center" : dx > 0 ? "left" : "right";
110            ctx.fillStyle = `rgb(${a.c})`;
111            ctx.beginPath();
112            ctx.arc(P[i][0], P[i][1], 3.5, 0, 7);
113            ctx.fill();
114            ctx.lineWidth = 4;
115            ctx.strokeStyle = "rgba(246,239,224,0.9)";
116            ctx.lineJoin = "round";
117            const lines = a.name.split(" "), y0 = y - ((lines.length - 1) * 14) / 2 + (dy > l * 0.35 ? 7 : dy < -l * 0.35 ? -7 : 0);
118            lines.forEach((ln, k) => ctx.strokeText(ln, x, y0 + k * 14));
119            ctx.fillStyle = "#1c1b2c";
120            lines.forEach((ln, k) => ctx.fillText(ln, x, y0 + k * 14));
121        });
122        ctx.globalAlpha = 1;
123        shown = pos.map((p, i) => ({ i, q: scr(rot(p.p)) })).sort((a, b) => a.q[2] - b.q[2]);
124        for (const { i, q } of shown) {
125            const p = pos[i], R = hover === i ? 7.5 : 6;
126            ctx.globalAlpha = 0.55 + 0.45 * Math.min(1, Math.max(0, (q[2] + 1) / 2));
127            ctx.beginPath();
128            ctx.arc(q[0], q[1], R, 0, 7);
129            ctx.fillStyle = `rgb(${p.c})`;
130            ctx.fill();
131            ctx.lineWidth = 2;
132            ctx.strokeStyle = "#f6efe0";
133            ctx.stroke();
134        }
135        ctx.globalAlpha = 1;
136        if (hover >= 0) {
137            const p = pos[hover], q = shown.find((x) => x.i === hover).q;
138            let fs = 14;
139            const font = () => (ctx.font = `700 ${fs}px 'Fira Sans Condensed', sans-serif`);
140            font();
141            let tw = ctx.measureText(p.title).width;
142            const d = 16, R0 = 8, gap = R0 * 0.7 + d + 6;
143            const roomR = W - q[0] - gap, roomL = q[0] - gap;
144            let right = tw <= roomR && (tw > roomL || roomR >= roomL) ? true : tw <= roomL ? false : roomR >= roomL;
145            const room = right ? roomR : roomL;
146            let lines = [p.title];
147            if (tw > room) {
148                fs = Math.max(12, Math.floor((fs * room) / tw));
149                font();
150                tw = ctx.measureText(p.title).width;
151                if (tw > room) {
152                    const words = p.title.split(" ");
153                    let best = null;
154                    for (let i = 1; i < words.length; i++) {
155                        const ls = [words.slice(0, i).join(" "), words.slice(i).join(" ")], w = Math.max(...ls.map((l) => ctx.measureText(l).width));
156                        if (!best || w < best.w) best = { ls, w };
157                    }
158                    lines = best.ls;
159                    tw = best.w;
160                }
161            }
162            const k = still.matches ? 1 : Math.max(0, Math.min(1, (performance.now() - hoverT) / 420)), e = 1 - (1 - k) ** 3;
163            const lh = fs * 1.2, th2 = lh * lines.length;
164            const up = q[1] - d - th2 - 6 > 0, sx = right ? 1 : -1, sy = up ? -1 : 1;
165            const x0 = q[0] + sx * R0 * 0.7, y0 = q[1] + sy * R0 * 0.7, x1 = x0 + sx * d, y1 = y0 + sy * d;
166            const L = tw + 4, a1 = Math.min(1, e / 0.3), a2 = Math.max(0, (e - 0.3) / 0.7);
167            ctx.beginPath();
168            ctx.moveTo(x0, y0);
169            ctx.lineTo(x0 + sx * d * a1, y0 + sy * d * a1);
170            if (a2 > 0) ctx.lineTo(x1 + sx * L * a2, y1);
171            ctx.lineWidth = 3.5;
172            ctx.lineJoin = "round";
173            ctx.strokeStyle = "rgba(246,239,224,0.85)";
174            ctx.stroke();
175            ctx.lineWidth = 1.2;
176            ctx.strokeStyle = "#1c1b2c";
177            ctx.stroke();
178            if (a2 > 0) {
179                ctx.save();
180                ctx.beginPath();
181                ctx.rect(right ? x1 : x1 - L * a2, y1 - th2 - 6, L * a2, th2 + 6);
182                ctx.clip();
183                ctx.textAlign = right ? "left" : "right";
184                ctx.textBaseline = "alphabetic";
185                const tx = x1 + sx * 2, ty = y1 - 4;
186                ctx.lineWidth = 4;
187                ctx.strokeStyle = "rgba(246,239,224,0.85)";
188                lines.forEach((ln, i) => ctx.strokeText(ln, tx, ty - (lines.length - 1 - i) * lh));
189                ctx.fillStyle = "#1c1b2c";
190                lines.forEach((ln, i) => ctx.fillText(ln, tx, ty - (lines.length - 1 - i) * lh));
191                ctx.restore();
192            }
193        }
194    }
195
196    function tick(t) {
197        if (!live) return;
198        const dt = last ? Math.min(0.05, (t - last) / 1000) : 0;
199        last = t;
200        if (!drag && !still.matches && hover < 0) th += dt * 0.25;
201        draw();
202        requestAnimationFrame(tick);
203    }
204
205    const hit = (e) => {
206        const b = cv.getBoundingClientRect(), x = e.clientX - b.left, y = e.clientY - b.top;
207        for (let k = shown.length - 1; k >= 0; k--) if (Math.hypot(shown[k].q[0] - x, shown[k].q[1] - y) < 11) return shown[k].i;
208        return -1;
209    };
210    cv.addEventListener("pointerdown", (e) => {
211        drag = { x: e.clientX, y: e.clientY, th, ph, moved: false };
212        cv.setPointerCapture(e.pointerId);
213    });
214    cv.addEventListener("pointermove", (e) => {
215        if (drag) {
216            const dx = e.clientX - drag.x, dy = e.clientY - drag.y;
217            if (Math.hypot(dx, dy) > 3) drag.moved = true;
218            th = drag.th + dx * 0.012;
219            ph = Math.max(-1.2, Math.min(1.2, drag.ph + dy * 0.012));
220            if (still.matches) draw();
221            return;
222        }
223        const h = hit(e);
224        if (h !== hover) {
225            hover = h;
226            hoverT = performance.now();
227            cv.style.cursor = h < 0 ? "grab" : "pointer";
228            if (still.matches) draw();
229        }
230    });
231    cv.addEventListener("pointerup", (e) => {
232        const moved = drag && drag.moved;
233        drag = null;
234        if (!moved) {
235            const h = hit(e);
236            if (h >= 0) location.href = pos[h].href;
237        }
238    });
239    cv.addEventListener("pointerleave", () => {
240        hover = -1;
241    });
242    new IntersectionObserver(([e]) => {
243        const was = live;
244        live = e.isIntersecting && cv.clientWidth > 0;
245        if (live && !was) {
246            last = 0;
247            requestAnimationFrame(tick);
248        }
249    }).observe(cv);
250    addEventListener("resize", () => {
251        size();
252        draw();
253    });
254    size();
255    document.fonts.ready.then(() => {
256        size();
257        draw();
258    });
259}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.