procedural-paper 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +205 -2
- package/THIRD_PARTY_NOTICES.md +51 -0
- package/dist/cards.d.ts +42 -0
- package/dist/color.d.ts +5 -0
- package/dist/composition/assets.d.ts +7 -0
- package/dist/composition/cords.d.ts +43 -0
- package/dist/composition/filters.d.ts +1 -0
- package/dist/composition/fit.d.ts +58 -0
- package/dist/composition/index.d.ts +27 -0
- package/dist/composition/index.js +1247 -0
- package/dist/composition/index.js.map +1 -0
- package/dist/composition/model.d.ts +72 -0
- package/dist/composition/motion-spec.d.ts +98 -0
- package/dist/composition/motion.d.ts +36 -0
- package/dist/composition/motion.js +291 -0
- package/dist/composition/motion.js.map +1 -0
- package/dist/composition/nodes.d.ts +7 -0
- package/dist/composition/pen.d.ts +6 -0
- package/dist/composition/photos.d.ts +41 -0
- package/dist/composition/props.d.ts +32 -0
- package/dist/composition/react.d.ts +10 -0
- package/dist/composition/react.js +22 -0
- package/dist/composition/react.js.map +1 -0
- package/dist/composition/renderer.d.ts +77 -0
- package/dist/composition/rng.d.ts +11 -0
- package/dist/composition/scene.d.ts +77 -0
- package/dist/composition/unrotate.d.ts +3 -0
- package/dist/composition.css +78 -0
- package/dist/cpu/compose.d.ts +7 -0
- package/dist/cpu/field.d.ts +10 -0
- package/dist/cpu/noise.d.ts +7 -0
- package/dist/cpu/overlays.d.ts +12 -0
- package/dist/cpu/renderer.d.ts +16 -0
- package/dist/cpu/strands.d.ts +33 -0
- package/dist/dom.d.ts +110 -0
- package/dist/dom.js +65 -0
- package/dist/dom.js.map +1 -0
- package/dist/export.d.ts +20 -0
- package/dist/gl/renderer.d.ts +61 -0
- package/dist/gl/shaders.d.ts +10 -0
- package/dist/gl/sheet-shader.d.ts +1 -0
- package/dist/hash.d.ts +12 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +2415 -0
- package/dist/index.js.map +1 -0
- package/dist/params.d.ts +169 -0
- package/dist/presets.d.ts +11 -0
- package/dist/react.d.ts +20 -0
- package/dist/react.js +50 -0
- package/dist/react.js.map +1 -0
- package/dist/resolve.d.ts +125 -0
- package/dist/sheet.d.ts +167 -0
- package/dist/stocks.d.ts +12 -0
- package/dist/styles.css +13 -0
- package/licenses/Apache-2.0.txt +201 -0
- package/package.json +116 -4
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
import { parseColor as tt, mergeParams as gt, stockParams as st, PaperRenderer as vt } from "../index.js";
|
|
2
|
+
import { renderPaperLayers as xt } from "../dom.js";
|
|
3
|
+
import { layout as wt, prepare as yt } from "@chenglou/pretext";
|
|
4
|
+
const ot = 100, lt = /* @__PURE__ */ new Map(), ct = /* @__PURE__ */ new Map();
|
|
5
|
+
function ft(t, n, r) {
|
|
6
|
+
return `${n} ${r}px "${t.family}"`;
|
|
7
|
+
}
|
|
8
|
+
function bt(t, n) {
|
|
9
|
+
const r = `${n} ${t.family}`;
|
|
10
|
+
let o = lt.get(r);
|
|
11
|
+
return o || (o = document.fonts.load(ft(t, n, 20), "abc ☐☑").catch(() => {
|
|
12
|
+
}), lt.set(r, o)), o;
|
|
13
|
+
}
|
|
14
|
+
function Mt(t, n, r, o, i) {
|
|
15
|
+
const c = `${n.family}|${r}|${o}|${i}|${t}`;
|
|
16
|
+
let m = ct.get(c);
|
|
17
|
+
return m || (m = yt(t, ft(n, r, ot), { whiteSpace: i, letterSpacing: o * ot }), ct.set(c, m)), m;
|
|
18
|
+
}
|
|
19
|
+
function dt(t, n, r) {
|
|
20
|
+
const o = t.weight ?? "400", i = t.whiteSpace ?? "normal";
|
|
21
|
+
let c = 0, m = 0;
|
|
22
|
+
for (let f = 0; f < r; f++) {
|
|
23
|
+
const s = t.blocks[f], g = n * (s.scale ?? 1), y = Mt(s.text, t.font, s.weight ?? o, s.spacing ?? 0, i), x = s.text ? wt(y, t.width * ot / g, 1).lineCount : 1, K = t.pitch ?? g * (t.lineHeight ?? 1.2);
|
|
24
|
+
c += x * K + (s.gap ?? 0) * g, m += x;
|
|
25
|
+
}
|
|
26
|
+
return { h: c, lines: m };
|
|
27
|
+
}
|
|
28
|
+
async function Et(t) {
|
|
29
|
+
await Promise.all([t.weight ?? "400", ...t.blocks.map((m) => m.weight).filter(Boolean)].map((m) => bt(t.font, m)));
|
|
30
|
+
const n = (m, f) => {
|
|
31
|
+
const s = dt(t, m, f);
|
|
32
|
+
return s.h <= t.height + 0.5 && (t.maxLines === void 0 || s.lines <= t.maxLines);
|
|
33
|
+
};
|
|
34
|
+
let r = t.blocks.length;
|
|
35
|
+
if (t.drop !== !1)
|
|
36
|
+
for (; r > 1 && !n(t.min, r); ) {
|
|
37
|
+
let m = r - 1;
|
|
38
|
+
for (; m > 0 && t.blocks[m].keep; ) m--;
|
|
39
|
+
if (m === 0) break;
|
|
40
|
+
t.blocks.splice(m, 1), r--;
|
|
41
|
+
}
|
|
42
|
+
let o = t.min, i = t.max;
|
|
43
|
+
if (n(i, r)) o = i;
|
|
44
|
+
else
|
|
45
|
+
for (let m = 0; m < 14; m++) {
|
|
46
|
+
const f = (o + i) / 2;
|
|
47
|
+
n(f, r) ? o = f : i = f;
|
|
48
|
+
}
|
|
49
|
+
const c = Math.floor(o * 4) / 4;
|
|
50
|
+
return { size: c, kept: r, lines: dt(t, c, r).lines };
|
|
51
|
+
}
|
|
52
|
+
const Ct = (t) => t.replace(/&/g, "&").replace(/</g, "<"), at = /* @__PURE__ */ new WeakMap(), Q = /* @__PURE__ */ new WeakMap();
|
|
53
|
+
function ht(t) {
|
|
54
|
+
return t.map((n) => {
|
|
55
|
+
const r = [n.scale && n.scale !== 1 ? `font-size:${n.scale}em` : "", n.gap ? `margin-top:${n.gap}em` : "", n.spacing ? `letter-spacing:${n.spacing}em` : "", n.weight ? `font-weight:${n.weight}` : ""].filter(Boolean).join(";");
|
|
56
|
+
return `<div class="${n.cls ?? ""}"${r ? ` style="${r}"` : ""}>${n.html ?? Ct(n.text)}</div>`;
|
|
57
|
+
}).join("");
|
|
58
|
+
}
|
|
59
|
+
function At(t, n) {
|
|
60
|
+
t.innerHTML = ht(n.blocks), t.style.fontSize = `${n.max}px`, n.pitch ? t.style.lineHeight = `${n.pitch}px` : t.style.lineHeight = `${n.lineHeight ?? 1.2}`, t.style.width = `${n.vertical ? n.height : n.width}px`, t.style.height = `${n.vertical ? n.width : n.height}px`, t.classList.add("fit"), Q.set(t, n), at.set(t, n);
|
|
61
|
+
}
|
|
62
|
+
async function St(t) {
|
|
63
|
+
const n = [t, ...t.querySelectorAll(".fit")].filter((r) => Q.has(r));
|
|
64
|
+
for (const r of n) {
|
|
65
|
+
const o = Q.get(r), i = o.blocks.length, c = await Et(o);
|
|
66
|
+
r.innerHTML = ht(o.blocks.slice(0, c.kept)), r.style.fontSize = `${c.size}px`, r.dataset.fit = `${c.size}px, ${c.lines} lines${c.kept < i ? `, dropped ${i - c.kept}` : ""}`, Q.delete(r);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const kt = { jute: 0, hemp: 1, baker: 2, cotton: 3, satin: 4, grosgrain: 5, wire: 6 }, Z = 96, Rt = (
|
|
70
|
+
/* glsl */
|
|
71
|
+
`#version 300 es
|
|
72
|
+
precision highp float;
|
|
73
|
+
#define MAXP ${Z}
|
|
74
|
+
uniform vec4 uP[MAXP]; // x, y, z, layer (1 on top, 2 under); negative = first point of a polyline
|
|
75
|
+
uniform vec4 uA[MAXP]; // arc length at point, polyline length, free-end bits, polyline index
|
|
76
|
+
uniform int uN;
|
|
77
|
+
uniform int uPart; // 1 front, 2 back
|
|
78
|
+
uniform vec3 uKnots[4];
|
|
79
|
+
uniform int uKnotN;
|
|
80
|
+
uniform vec2 uOrigin;
|
|
81
|
+
uniform float uScale;
|
|
82
|
+
uniform vec3 uLight;
|
|
83
|
+
uniform int uKind;
|
|
84
|
+
uniform vec3 uC1, uC2;
|
|
85
|
+
uniform float uW;
|
|
86
|
+
uniform float uSeed;
|
|
87
|
+
out vec4 outColor;
|
|
88
|
+
|
|
89
|
+
const int JUTE = 0, HEMP = 1, BAKER = 2, COTTON = 3, SATIN = 4, GROS = 5, WIRE = 6;
|
|
90
|
+
|
|
91
|
+
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7)) + uSeed * 17.3) * 43758.5453); }
|
|
92
|
+
float vnoise(vec2 p) {
|
|
93
|
+
vec2 i = floor(p), f = fract(p);
|
|
94
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
95
|
+
return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
bool ribbon() { return uKind == SATIN || uKind == GROS; }
|
|
99
|
+
float plies() { return uKind == BAKER ? 2.0 : (uKind == COTTON ? 4.0 : (uKind == WIRE ? 1.0 : 3.0)); }
|
|
100
|
+
float pitch() { return uW * (uKind == COTTON ? 1.2 : (uKind == BAKER ? 2.4 : 1.9)); }
|
|
101
|
+
|
|
102
|
+
// height of the cord surface above its centre-line height, in local (t along, s across in -1..1)
|
|
103
|
+
float surf(float t, float s) {
|
|
104
|
+
float r = uW * 0.5;
|
|
105
|
+
float a = max(1.0 - s * s, 0.0);
|
|
106
|
+
if (ribbon()) {
|
|
107
|
+
// flat band: nearly no camber, crisp edges; grosgrain has fine ribs across its width
|
|
108
|
+
float h = r * 0.04 * a;
|
|
109
|
+
if (uKind == GROS) h += 0.16 * sin(t * 6.2831 / 1.1);
|
|
110
|
+
return h;
|
|
111
|
+
}
|
|
112
|
+
float h = r * sqrt(a);
|
|
113
|
+
if (uKind != WIRE) {
|
|
114
|
+
float n = plies();
|
|
115
|
+
float g = fract((t / pitch() + s * 0.45) * n);
|
|
116
|
+
float groove = 1.0 - smoothstep(0.0, 0.28, g) * smoothstep(1.0, 0.72, g);
|
|
117
|
+
h -= groove * r * (uKind == COTTON ? 0.18 : 0.38) * sqrt(a);
|
|
118
|
+
}
|
|
119
|
+
return h;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
struct Hit { float h; float d; float t; float s; float z; float len; float ends; vec2 tan; float side; float knot; };
|
|
123
|
+
|
|
124
|
+
Hit nearest(vec2 p, int part) {
|
|
125
|
+
Hit best = Hit(-1e9, 1e9, 0.0, 0.0, 0.0, 1.0, 0.0, vec2(1.0, 0.0), 1.0, 0.0);
|
|
126
|
+
float r = uW * 0.5;
|
|
127
|
+
float bestD = 1e9;
|
|
128
|
+
for (int i = 1; i < MAXP; i++) {
|
|
129
|
+
if (i >= uN) break;
|
|
130
|
+
vec4 b = uP[i];
|
|
131
|
+
if (b.w < 0.0) continue;
|
|
132
|
+
vec4 a = uP[i - 1];
|
|
133
|
+
if (part != 0 && int(abs(a.w) + 0.5) != part) continue;
|
|
134
|
+
vec2 ab = b.xy - a.xy;
|
|
135
|
+
float L = length(ab);
|
|
136
|
+
if (L < 1e-4) continue;
|
|
137
|
+
vec2 td = ab / L;
|
|
138
|
+
float u = clamp(dot(p - a.xy, td), 0.0, L);
|
|
139
|
+
vec2 dv = p - (a.xy + td * u);
|
|
140
|
+
float d = length(dv);
|
|
141
|
+
if (d > r + 3.0) continue;
|
|
142
|
+
float side = (td.x * dv.y - td.y * dv.x) >= 0.0 ? 1.0 : -1.0;
|
|
143
|
+
float z = mix(a.z, b.z, u / L);
|
|
144
|
+
float t = uA[i - 1].x + u;
|
|
145
|
+
float s = side * d / r;
|
|
146
|
+
float h = d < r ? z + surf(t, s) : -1e6 + z - d;
|
|
147
|
+
if (h > best.h || (best.h < -1e5 && d < bestD)) {
|
|
148
|
+
best = Hit(h, d, t, s, z, uA[i - 1].y, uA[i - 1].z, td, side, 0.0);
|
|
149
|
+
bestD = d;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (int k = 0; k < 4; k++) {
|
|
153
|
+
if (k >= uKnotN || part == 2) break;
|
|
154
|
+
vec3 kn = uKnots[k];
|
|
155
|
+
vec2 dv = p - kn.xy;
|
|
156
|
+
float d = length(dv);
|
|
157
|
+
if (d < kn.z) {
|
|
158
|
+
float h = 0.4 + kn.z * 0.75 * sqrt(1.0 - (d / kn.z) * (d / kn.z));
|
|
159
|
+
if (h > best.h) best = Hit(h, d, atan(dv.y, dv.x) * kn.z * 1.3, d / kn.z, 0.4, 1e3, 0.0, normalize(vec2(-dv.y, dv.x) + 1e-5), 1.0, 1.0);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return best;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
vec2 local(vec2 px) { return (px - uOrigin) / uScale; }
|
|
166
|
+
|
|
167
|
+
void main() {
|
|
168
|
+
vec2 p = local(gl_FragCoord.xy);
|
|
169
|
+
Hit o = nearest(p, uPart);
|
|
170
|
+
float r = uW * 0.5;
|
|
171
|
+
vec3 L = normalize(uLight);
|
|
172
|
+
vec2 ld = normalize(L.xy);
|
|
173
|
+
float tanE = L.z / max(length(L.xy), 1e-3);
|
|
174
|
+
|
|
175
|
+
float alpha = 0.0;
|
|
176
|
+
vec3 col = vec3(0.0);
|
|
177
|
+
bool fuzzy = uKind == JUTE || uKind == HEMP || uKind == BAKER;
|
|
178
|
+
if (o.d < r + 3.0) {
|
|
179
|
+
alpha = clamp((r - o.d) * uScale + 0.5, 0.0, 1.0);
|
|
180
|
+
// free ends: twine frays (plies part), ribbon gets a V / slanted cut
|
|
181
|
+
float fromStart = o.t, fromEnd = o.len - o.t;
|
|
182
|
+
bool startFree = mod(o.ends, 2.0) >= 1.0, endFree = o.ends >= 2.0;
|
|
183
|
+
float endDist = min(startFree ? fromStart : 1e3, endFree ? fromEnd : 1e3);
|
|
184
|
+
if (o.knot < 0.5) {
|
|
185
|
+
if (ribbon()) {
|
|
186
|
+
float notch = r * 1.6;
|
|
187
|
+
if (endDist < notch * (1.0 - abs(o.s)) + (o.s * 0.5 + 0.5) * r * 0.25) alpha = 0.0;
|
|
188
|
+
} else if (endDist < uW * 2.2 && uKind != WIRE) {
|
|
189
|
+
float g = fract((o.t / pitch() + o.s * 0.45) * plies());
|
|
190
|
+
float gap = smoothstep(uW * 2.2, 0.0, endDist);
|
|
191
|
+
alpha *= smoothstep(0.0, 0.25 * gap + 0.01, g) * smoothstep(1.0, 1.0 - 0.25 * gap - 0.01, g);
|
|
192
|
+
} else if (endDist < 0.6) alpha = 0.0;
|
|
193
|
+
}
|
|
194
|
+
// stray fibres around twine
|
|
195
|
+
if (fuzzy && o.d >= r - 0.3) {
|
|
196
|
+
float k = o.d - r;
|
|
197
|
+
float hair = smoothstep(0.62, 0.8, vnoise(vec2(o.t * 0.45, k * 0.9 + o.side * 11.0)) * (1.0 - k / 3.0));
|
|
198
|
+
alpha = max(alpha, hair * 0.75);
|
|
199
|
+
}
|
|
200
|
+
if (alpha > 0.0) {
|
|
201
|
+
// normal from the local surface: across (s) and along (t)
|
|
202
|
+
float e = 0.08;
|
|
203
|
+
float hs = (surf(o.t, clamp(o.s + e, -1.0, 1.0)) - surf(o.t, clamp(o.s - e, -1.0, 1.0))) / (2.0 * e * r);
|
|
204
|
+
float ht = (surf(o.t + 0.2, o.s) - surf(o.t - 0.2, o.s)) / 0.4;
|
|
205
|
+
vec2 perp = vec2(-o.tan.y, o.tan.x);
|
|
206
|
+
vec2 grad = perp * hs + o.tan * ht;
|
|
207
|
+
if (o.knot > 0.5) {
|
|
208
|
+
// knots are little domes
|
|
209
|
+
float q = min(o.s, 0.98);
|
|
210
|
+
grad = vec2(o.tan.y, -o.tan.x) * (-0.75 * q / sqrt(1.0 - q * q)) + o.tan * ht;
|
|
211
|
+
}
|
|
212
|
+
if (uKind == SATIN) grad += o.tan * (0.35 * sin(o.t / 17.0 + uSeed) + 0.18 * sin(o.t / 6.3 + uSeed * 2.0));
|
|
213
|
+
if (uKind == GROS) grad += o.tan * 0.15 * sin(o.t / 21.0 + uSeed);
|
|
214
|
+
vec3 n = normalize(vec3(-grad, 1.0));
|
|
215
|
+
vec3 V = vec3(0.0, 0.0, 1.0);
|
|
216
|
+
vec3 H = normalize(L + V);
|
|
217
|
+
float dif = max(dot(n, L), 0.0);
|
|
218
|
+
float nh = max(dot(n, H), 0.0);
|
|
219
|
+
|
|
220
|
+
vec3 base = uC1;
|
|
221
|
+
float n2 = plies();
|
|
222
|
+
float ply = mod(floor((o.t / pitch() + o.s * 0.45) * n2), n2);
|
|
223
|
+
if (uKind == BAKER) base = ply < 0.5 ? uC1 : uC2;
|
|
224
|
+
else if (uKind == JUTE || uKind == HEMP) base *= 0.9 + 0.08 * ply + 0.2 * (vnoise(vec2(o.t * 0.9, o.s * 3.0 + ply * 5.0)) - 0.5);
|
|
225
|
+
else if (uKind == COTTON) base *= 0.96 + 0.06 * vnoise(vec2(o.t * 1.4, o.s * 4.0));
|
|
226
|
+
if (ribbon()) base *= 1.0 - 0.07 * smoothstep(0.86, 1.0, abs(o.s));
|
|
227
|
+
if (uKind == GROS) base *= 0.92 + 0.08 * sin(o.t * 6.2831 / 1.1);
|
|
228
|
+
if (o.d >= r) base = mix(base, vec3(1.0), 0.15);
|
|
229
|
+
|
|
230
|
+
if (uKind == WIRE) {
|
|
231
|
+
vec3 rf = reflect(-V, n);
|
|
232
|
+
float env = 0.35 + 0.35 * rf.z + 0.7 * max(dot(normalize(rf.xy + 1e-5), ld), 0.0) * length(rf.xy);
|
|
233
|
+
col = base * env + vec3(1.0) * pow(nh, 80.0) * 1.2;
|
|
234
|
+
} else if (uKind == SATIN) {
|
|
235
|
+
col = base * (0.38 + 0.62 * dif) + mix(base, vec3(1.0), 0.55) * pow(nh, 26.0) * 0.85 + base * 0.12 * pow(1.0 - n.z, 0.5);
|
|
236
|
+
} else {
|
|
237
|
+
float spec = uKind == GROS ? 0.08 : 0.05;
|
|
238
|
+
col = base * (0.36 + 0.74 * dif) + vec3(1.0) * pow(nh, 18.0) * spec;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// short cast shadow: the cord's footprint shifted away from the light by its height
|
|
244
|
+
float shadow = 0.0;
|
|
245
|
+
if (alpha < 1.0) {
|
|
246
|
+
float hgt = (ribbon() ? 0.6 : r * 1.6) + 0.3;
|
|
247
|
+
Hit s = nearest(p + ld * hgt / tanE, uPart);
|
|
248
|
+
float reach = s.knot > 0.5 ? uW * 1.2 : r;
|
|
249
|
+
shadow = smoothstep(reach + 1.2, reach - 0.6, s.d) * 0.34;
|
|
250
|
+
}
|
|
251
|
+
float outA = alpha + shadow * (1.0 - alpha);
|
|
252
|
+
outColor = vec4(outA > 0.0 ? clamp(col, 0.0, 1.0) * alpha / outA : vec3(0.0), outA);
|
|
253
|
+
}
|
|
254
|
+
`
|
|
255
|
+
), Tt = `#version 300 es
|
|
256
|
+
in vec2 aPos;
|
|
257
|
+
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }`;
|
|
258
|
+
function Lt() {
|
|
259
|
+
let t = null, n;
|
|
260
|
+
const r = /* @__PURE__ */ new Map(), o = (m) => (r.has(m) || r.set(m, t.getUniformLocation(n, m)), r.get(m));
|
|
261
|
+
function i() {
|
|
262
|
+
const f = new OffscreenCanvas(1, 1).getContext("webgl2", { premultipliedAlpha: !1, antialias: !1, preserveDrawingBuffer: !0 });
|
|
263
|
+
if (!f) throw new Error("WebGL2 not available");
|
|
264
|
+
t = f;
|
|
265
|
+
const s = (g, y) => {
|
|
266
|
+
const x = f.createShader(g);
|
|
267
|
+
if (f.shaderSource(x, y), f.compileShader(x), !f.getShaderParameter(x, f.COMPILE_STATUS)) throw new Error(f.getShaderInfoLog(x) ?? "cord shader error");
|
|
268
|
+
return x;
|
|
269
|
+
};
|
|
270
|
+
if (n = f.createProgram(), f.attachShader(n, s(f.VERTEX_SHADER, Tt)), f.attachShader(n, s(f.FRAGMENT_SHADER, Rt)), f.bindAttribLocation(n, 0, "aPos"), f.linkProgram(n), !f.getProgramParameter(n, f.LINK_STATUS)) throw new Error(f.getProgramInfoLog(n) ?? "cord link error");
|
|
271
|
+
f.bindVertexArray(f.createVertexArray()), f.bindBuffer(f.ARRAY_BUFFER, f.createBuffer()), f.bufferData(f.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), f.STATIC_DRAW), f.enableVertexAttribArray(0), f.vertexAttribPointer(0, 2, f.FLOAT, !1, 0, 0);
|
|
272
|
+
}
|
|
273
|
+
function c(m, f, s, g, y) {
|
|
274
|
+
t || i();
|
|
275
|
+
const x = t, K = new Float32Array(Z * 4), z = new Float32Array(Z * 4);
|
|
276
|
+
let C = 0, d = 1 / 0, l = 1 / 0, w = -1 / 0, k = -1 / 0, I = !1;
|
|
277
|
+
if (m.lines.forEach((a, p) => {
|
|
278
|
+
let u = 0;
|
|
279
|
+
const h = C;
|
|
280
|
+
a.pts.forEach((v, M) => {
|
|
281
|
+
if (C >= Z) return;
|
|
282
|
+
M > 0 && (u += Math.hypot(v[0] - a.pts[M - 1][0], v[1] - a.pts[M - 1][1]));
|
|
283
|
+
const S = a.under[M] ? 2 : 1;
|
|
284
|
+
if (K.set([v[0], v[1], v[2], M === 0 ? -S : S], C * 4), z.set([u, 0, (a.freeStart ? 1 : 0) + (a.freeEnd ? 2 : 0), p], C * 4), M < a.pts.length - 1 && S === 2 == (f === "back")) {
|
|
285
|
+
I = !0;
|
|
286
|
+
for (const E of [v, a.pts[M + 1]])
|
|
287
|
+
d = Math.min(d, E[0]), w = Math.max(w, E[0]), l = Math.min(l, E[1]), k = Math.max(k, E[1]);
|
|
288
|
+
}
|
|
289
|
+
C++;
|
|
290
|
+
});
|
|
291
|
+
for (let v = h; v < C; v++) z[v * 4 + 1] = u;
|
|
292
|
+
}), f === "front")
|
|
293
|
+
for (const [a, p, u] of m.knots)
|
|
294
|
+
I = !0, d = Math.min(d, a - u), w = Math.max(w, a + u), l = Math.min(l, p - u), k = Math.max(k, p + u);
|
|
295
|
+
if (!I) return null;
|
|
296
|
+
const L = m.width + 8;
|
|
297
|
+
d -= L, l -= L, w += L, k += L;
|
|
298
|
+
const P = Math.ceil(w - d), $ = Math.ceil(k - l), F = Math.ceil(P * s), b = Math.ceil($ * s);
|
|
299
|
+
x.canvas.width = F, x.canvas.height = b, x.viewport(0, 0, F, b), x.useProgram(n), x.uniform4fv(o("uP"), K), x.uniform4fv(o("uA"), z), x.uniform1i(o("uN"), C), x.uniform1i(o("uPart"), f === "front" ? 1 : 2);
|
|
300
|
+
const R = new Float32Array(12);
|
|
301
|
+
m.knots.slice(0, 4).forEach((a, p) => R.set(a, p * 3)), x.uniform3fv(o("uKnots"), R), x.uniform1i(o("uKnotN"), Math.min(4, m.knots.length)), x.uniform2f(o("uOrigin"), -d * s, -l * s), x.uniform1f(o("uScale"), s);
|
|
302
|
+
const T = y * Math.PI / 180, O = g * Math.PI / 180;
|
|
303
|
+
x.uniform3f(o("uLight"), Math.cos(T) * Math.cos(O), Math.cos(T) * Math.sin(O), Math.sin(T)), x.uniform1i(o("uKind"), kt[m.kind]);
|
|
304
|
+
const B = tt(m.colors[0]), D = tt(m.colors[1]);
|
|
305
|
+
x.uniform3f(o("uC1"), B[0], B[1], B[2]), x.uniform3f(o("uC2"), D[0], D[1], D[2]), x.uniform1f(o("uW"), m.width), x.uniform1f(o("uSeed"), m.seed % 97), x.drawArrays(x.TRIANGLES, 0, 3);
|
|
306
|
+
const q = new Uint8ClampedArray(F * b * 4);
|
|
307
|
+
x.readPixels(0, 0, F, b, x.RGBA, x.UNSIGNED_BYTE, q);
|
|
308
|
+
const e = document.createElement("canvas");
|
|
309
|
+
return e.width = F, e.height = b, e.getContext("2d").putImageData(new ImageData(q, F, b), 0, 0), e.style.width = `${P}px`, e.style.height = `${$}px`, { canvas: e, w: P, h: $, ox: -d, oy: -l };
|
|
310
|
+
}
|
|
311
|
+
return { renderCord: c, dispose() {
|
|
312
|
+
t?.getExtension("WEBGL_lose_context")?.loseContext(), t = null, r.clear();
|
|
313
|
+
} };
|
|
314
|
+
}
|
|
315
|
+
const Pt = {
|
|
316
|
+
pushpin: 0,
|
|
317
|
+
mappin: 1,
|
|
318
|
+
thumbtack: 2,
|
|
319
|
+
magnet: 3,
|
|
320
|
+
"magnet-neo": 4,
|
|
321
|
+
"magnet-bar": 5,
|
|
322
|
+
binder: 6,
|
|
323
|
+
paperclip: 7,
|
|
324
|
+
staple: 8,
|
|
325
|
+
pencil: 9,
|
|
326
|
+
ruler: 10,
|
|
327
|
+
eraser: 11,
|
|
328
|
+
"tape-roll": 12,
|
|
329
|
+
mug: 13,
|
|
330
|
+
knife: 14,
|
|
331
|
+
"washi-roll": 15,
|
|
332
|
+
eyelet: 16
|
|
333
|
+
}, pt = {
|
|
334
|
+
pushpin: [9.5, 15],
|
|
335
|
+
mappin: [6.5, 20],
|
|
336
|
+
thumbtack: [8, 2.6],
|
|
337
|
+
magnet: [14, 8],
|
|
338
|
+
"magnet-neo": [6.5, 3.2],
|
|
339
|
+
"magnet-bar": [19, 6.5],
|
|
340
|
+
binder: [34, 10],
|
|
341
|
+
paperclip: [21, 2.4],
|
|
342
|
+
staple: [8, 1.2],
|
|
343
|
+
pencil: [92, 9.5],
|
|
344
|
+
ruler: [158, 1.6],
|
|
345
|
+
eraser: [27, 10.5],
|
|
346
|
+
"tape-roll": [32, 17],
|
|
347
|
+
mug: [56, 84],
|
|
348
|
+
knife: [66, 9.5],
|
|
349
|
+
"washi-roll": [25, 15],
|
|
350
|
+
eyelet: [9.5, 1.4]
|
|
351
|
+
}, $t = `#version 300 es
|
|
352
|
+
in vec2 aPos;
|
|
353
|
+
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }`, It = (
|
|
354
|
+
/* glsl */
|
|
355
|
+
`#version 300 es
|
|
356
|
+
precision highp float;
|
|
357
|
+
uniform vec2 uOrigin; // object origin in sprite px
|
|
358
|
+
uniform float uScale; // device px per CSS px
|
|
359
|
+
uniform float uAngle;
|
|
360
|
+
uniform vec3 uLight;
|
|
361
|
+
uniform int uKind;
|
|
362
|
+
uniform vec3 uColor, uColor2;
|
|
363
|
+
uniform float uLength;
|
|
364
|
+
uniform int uPart;
|
|
365
|
+
uniform float uSeed;
|
|
366
|
+
uniform float uMaxH;
|
|
367
|
+
out vec4 outColor;
|
|
368
|
+
|
|
369
|
+
const int PLASTIC = 1, METAL = 2, BRASS = 3, MATTE = 4, WOOD = 5, GRAPHITE = 6, CERAMIC = 7, COFFEE = 8, PAINT = 9, STEEL = 10, CARD = 11;
|
|
370
|
+
|
|
371
|
+
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7)) + uSeed * 13.7) * 43758.5453); }
|
|
372
|
+
float vnoise(vec2 p) {
|
|
373
|
+
vec2 i = floor(p), f = fract(p);
|
|
374
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
375
|
+
return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// signed distances (negative inside)
|
|
379
|
+
float sdCircle(vec2 p, float r) { return length(p) - r; }
|
|
380
|
+
float sdBox(vec2 p, vec2 b, float r) { vec2 q = abs(p) - b + r; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r; }
|
|
381
|
+
float sdSeg(vec2 p, vec2 a, vec2 b) { vec2 pa = p - a, ba = b - a; float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); return length(pa - ba * h); }
|
|
382
|
+
// half-circle arc around c from angle a0 to a1 (radians)
|
|
383
|
+
float sdArc(vec2 p, vec2 c, float r, float a0, float a1) {
|
|
384
|
+
vec2 d = p - c;
|
|
385
|
+
float a = atan(d.y, d.x);
|
|
386
|
+
float mid = (a0 + a1) * 0.5, half_ = abs(a1 - a0) * 0.5;
|
|
387
|
+
float da = abs(mod(a - mid + 3.14159265, 6.2831853) - 3.14159265);
|
|
388
|
+
if (da <= half_) return abs(length(d) - r);
|
|
389
|
+
vec2 e0 = c + r * vec2(cos(a0), sin(a0)), e1 = c + r * vec2(cos(a1), sin(a1));
|
|
390
|
+
return min(length(p - e0), length(p - e1));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
struct Hit { float h; float sd; int mat; vec3 col; };
|
|
394
|
+
|
|
395
|
+
void add(inout Hit o, float sd, float h, int mat, vec3 col) {
|
|
396
|
+
if (sd < 0.0 && h > o.h) { o.h = h; o.mat = mat; o.col = col; }
|
|
397
|
+
o.sd = min(o.sd, sd);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// round tube of radius r lying with its top at height top: profile across distance d
|
|
401
|
+
float tube(float d, float r, float top) { return top - r + sqrt(max(r * r - d * d, 0.0)); }
|
|
402
|
+
float dome(float d, float R) { return sqrt(max(1.0 - (d / R) * (d / R), 0.0)); }
|
|
403
|
+
|
|
404
|
+
Hit scene(vec2 p) {
|
|
405
|
+
Hit o = Hit(0.0, 1e5, 0, vec3(0.0));
|
|
406
|
+
float d = length(p);
|
|
407
|
+
if (uKind == 0) { // push pin: flared grip, domed cap
|
|
408
|
+
float R = 9.0;
|
|
409
|
+
add(o, sdCircle(p, R), 11.0 + 2.2 * dome(d, R), PLASTIC, uColor);
|
|
410
|
+
if (d < R * 0.55) add(o, sdCircle(p, R * 0.55), 13.5 + 1.4 * dome(d, R * 0.55), PLASTIC, uColor * 1.08);
|
|
411
|
+
} else if (uKind == 1) { // map pin: sphere on a needle
|
|
412
|
+
add(o, sdCircle(p, 0.7), 14.0, METAL, vec3(0.8));
|
|
413
|
+
add(o, sdCircle(p, 5.8), 14.0 + 5.8 * dome(d, 5.8), PLASTIC, uColor);
|
|
414
|
+
} else if (uKind == 2) { // thumbtack
|
|
415
|
+
float R = 7.6;
|
|
416
|
+
float h = 1.1 + 1.2 * (1.0 - (d / R) * (d / R)) - 0.7 * exp(-(d / 1.3) * (d / 1.3));
|
|
417
|
+
add(o, sdCircle(p, R), h, BRASS, uColor);
|
|
418
|
+
} else if (uKind == 3) { // dome magnet
|
|
419
|
+
float R = 13.0;
|
|
420
|
+
add(o, sdCircle(p, R), 3.5 + 4.0 * pow(dome(d, R), 0.7), PLASTIC, uColor);
|
|
421
|
+
} else if (uKind == 4) { // neodymium disc
|
|
422
|
+
float R = 6.0;
|
|
423
|
+
add(o, sdCircle(p, R), 2.2 + 0.8 * smoothstep(R, R - 0.9, d), METAL, vec3(0.86, 0.87, 0.9));
|
|
424
|
+
} else if (uKind == 5) { // bar magnet
|
|
425
|
+
float sd = sdBox(p, vec2(17.0, 8.5), 3.0);
|
|
426
|
+
add(o, sd, 6.0 * smoothstep(0.0, -2.5, sd), PLASTIC, uColor);
|
|
427
|
+
} else if (uKind == 6) { // binder clip: jaw on the paper (+y), hinge past the edge (-y), handles out
|
|
428
|
+
float w = 15.0;
|
|
429
|
+
float sd = sdBox(p - vec2(0.0, -4.0), vec2(w, 10.0), 1.2);
|
|
430
|
+
float t = clamp((p.y + 14.0) / 20.0, 0.0, 1.0);
|
|
431
|
+
float bevel = smoothstep(0.0, -1.2, sd);
|
|
432
|
+
add(o, sd, mix(9.0, 1.8, t) * bevel, PAINT, uColor);
|
|
433
|
+
// folded-out wire handles
|
|
434
|
+
float wr = 1.1;
|
|
435
|
+
for (int s = -1; s <= 1; s += 2) {
|
|
436
|
+
vec2 a = vec2(float(s) * w * 0.72, -13.0), b = vec2(float(s) * w * 0.55, -30.0);
|
|
437
|
+
float dw = sdSeg(p, a, b);
|
|
438
|
+
float tt = clamp((-p.y - 13.0) / 17.0, 0.0, 1.0);
|
|
439
|
+
add(o, dw - wr, tube(dw, wr, mix(9.5, 3.0, tt)), METAL, vec3(0.9));
|
|
440
|
+
}
|
|
441
|
+
float db = sdSeg(p, vec2(-w * 0.55, -30.0), vec2(w * 0.55, -30.0));
|
|
442
|
+
add(o, db - wr, tube(db, wr, 3.0), METAL, vec3(0.9));
|
|
443
|
+
} else if (uKind == 7) { // gem paper clip, 10 x 36, origin at its centre
|
|
444
|
+
vec2 q = p + vec2(5.0, 18.0);
|
|
445
|
+
float r = 0.75;
|
|
446
|
+
float back = min(min(sdSeg(q, vec2(0.0, 8.0), vec2(0.0, 31.0)), sdArc(q, vec2(5.0, 31.0), 5.0, 0.0, 3.14159265)),
|
|
447
|
+
sdSeg(q, vec2(10.0, 31.0), vec2(10.0, 5.0)));
|
|
448
|
+
float front = min(min(sdArc(q, vec2(6.5, 5.0), 3.5, -3.14159265, 0.0), sdSeg(q, vec2(3.0, 5.0), vec2(3.0, 27.0))),
|
|
449
|
+
min(sdArc(q, vec2(5.0, 27.0), 2.0, 0.0, 3.14159265), sdSeg(q, vec2(7.0, 27.0), vec2(7.0, 11.0))));
|
|
450
|
+
float dd = uPart == 1 ? front : (uPart == 2 ? back : min(front, back));
|
|
451
|
+
add(o, dd - r, tube(dd, r, 1.6), METAL, uColor);
|
|
452
|
+
} else if (uKind == 8) { // staple crown
|
|
453
|
+
float dd = sdSeg(p, vec2(-6.0, 0.0), vec2(6.0, 0.0));
|
|
454
|
+
add(o, dd - 0.6, 0.9 + 0.2 * smoothstep(0.6, 0.0, dd), METAL, vec3(0.85));
|
|
455
|
+
} else if (uKind == 9) { // pencil along x, tip at -L/2
|
|
456
|
+
float L = uLength, w = 8.6, R = w * 0.5;
|
|
457
|
+
float x = p.x + L * 0.5; // 0 at the tip
|
|
458
|
+
float ay = abs(p.y);
|
|
459
|
+
float cone = 24.0;
|
|
460
|
+
if (x >= 0.0 && x < cone) {
|
|
461
|
+
float rr = R * x / cone;
|
|
462
|
+
if (ay < rr) {
|
|
463
|
+
bool lead = x < 6.0;
|
|
464
|
+
vec3 wood = uColor2;
|
|
465
|
+
add(o, ay - rr, R + sqrt(max(rr * rr - p.y * p.y, 0.0)) - (R - rr) * 0.6, lead ? GRAPHITE : WOOD, lead ? vec3(0.17) : wood);
|
|
466
|
+
}
|
|
467
|
+
o.sd = min(o.sd, ay - rr);
|
|
468
|
+
} else if (x >= cone && x <= L) {
|
|
469
|
+
float hexTop = R * 1.0 - max(0.0, ay - w * 0.25) * 1.15;
|
|
470
|
+
float sd = max(ay - R, max(cone - x, x - L));
|
|
471
|
+
int mat = PAINT; vec3 c = uColor;
|
|
472
|
+
if (x > L - 16.0 && x <= L - 6.0) { mat = METAL; c = vec3(0.82, 0.76, 0.55); hexTop = sqrt(max(R * R - p.y * p.y, 0.0)) * (0.92 + 0.08 * step(0.5, fract(x * 0.5))); }
|
|
473
|
+
else if (x > L - 6.0) { mat = MATTE; c = vec3(0.92, 0.55, 0.55); hexTop = sqrt(max(R * R - p.y * p.y, 0.0)) * 0.95; }
|
|
474
|
+
add(o, sd, R + hexTop * 0.95, mat, c);
|
|
475
|
+
}
|
|
476
|
+
} else if (uKind == 10) { // steel ruler
|
|
477
|
+
float L = uLength;
|
|
478
|
+
float sd = sdBox(p, vec2(L * 0.5, 13.0), 1.5);
|
|
479
|
+
vec3 c = vec3(0.78, 0.79, 0.8) * (0.93 + 0.07 * vnoise(vec2(p.x * 0.05, p.y * 3.0)));
|
|
480
|
+
float x = p.x + L * 0.5 - 8.0;
|
|
481
|
+
float mm = 3.7795;
|
|
482
|
+
float k = floor(x / mm + 0.5);
|
|
483
|
+
float tl = mod(k, 10.0) == 0.0 ? 9.0 : (mod(k, 5.0) == 0.0 ? 6.5 : 4.0);
|
|
484
|
+
if (x > -0.5 && x < L - 16.0 && abs(x - k * mm) < 0.35 && p.y < -13.0 + tl) c *= 0.25;
|
|
485
|
+
add(o, sd, 1.4 * smoothstep(0.0, -0.8, sd), STEEL, c);
|
|
486
|
+
} else if (uKind == 11) { // eraser with sleeve
|
|
487
|
+
float sd = sdBox(p, vec2(23.0, 10.0), 3.0);
|
|
488
|
+
float h = 9.5 * smoothstep(0.0, -3.5, sd);
|
|
489
|
+
bool sleeve = p.x > -8.0 && p.x < 20.0;
|
|
490
|
+
add(o, sd, h + (sleeve ? 0.4 : 0.0), sleeve ? CARD : MATTE, sleeve ? uColor : vec3(0.95, 0.94, 0.92));
|
|
491
|
+
} else if (uKind == 12 || uKind == 15) { // tape roll / washi roll seen from above
|
|
492
|
+
bool washi = uKind == 15;
|
|
493
|
+
float Ro = washi ? 24.0 : 31.0, Ri = washi ? 16.0 : 21.0, Rc = Ri - 2.6;
|
|
494
|
+
float H = washi ? 14.0 : 16.5;
|
|
495
|
+
float sdOut = d - Ro;
|
|
496
|
+
if (d > Ri && d < Ro) {
|
|
497
|
+
vec3 c = uColor * (0.93 + 0.07 * sin(d * 7.0 + vnoise(p * 0.3) * 3.0));
|
|
498
|
+
add(o, sdOut, H - 0.6 * smoothstep(Ro - 1.2, Ro, d) - 0.6 * smoothstep(Ri + 1.2, Ri, d), MATTE, c);
|
|
499
|
+
}
|
|
500
|
+
if (d > Rc && d <= Ri) add(o, -1.0, H + 0.4, CARD, vec3(0.62, 0.5, 0.37));
|
|
501
|
+
o.sd = min(o.sd, max(sdOut, Rc - d));
|
|
502
|
+
} else if (uKind == 13) { // mug: rim, coffee, handle
|
|
503
|
+
float Ro = 38.0, Ri = 33.5;
|
|
504
|
+
vec2 hp = p - vec2(Ro + 7.0, 0.0);
|
|
505
|
+
float dh = abs(length(hp * vec2(1.0, 0.8)) - 9.0);
|
|
506
|
+
if (p.x > Ro - 2.0) add(o, dh - 3.2, tube(dh, 3.2, 52.0), CERAMIC, uColor);
|
|
507
|
+
if (d < Ro) {
|
|
508
|
+
if (d > Ri) add(o, d - Ro, 80.0 + 1.5 * dome(abs(d - (Ro + Ri) * 0.5), (Ro - Ri) * 0.5), CERAMIC, uColor);
|
|
509
|
+
else {
|
|
510
|
+
float crema = smoothstep(Ri - 4.0, Ri - 0.5, d);
|
|
511
|
+
vec3 c = mix(vec3(0.17, 0.09, 0.05), vec3(0.42, 0.27, 0.15), crema * 0.7);
|
|
512
|
+
add(o, d - Ro, 66.0 + 2.0 * crema, COFFEE, c);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
o.sd = min(o.sd, d - Ro);
|
|
516
|
+
} else if (uKind == 16) { // metal eyelet: rolled ring around a 6px hole, see-through middle
|
|
517
|
+
float ri = 6.0, ro = 9.0;
|
|
518
|
+
if (d > ri - 0.4 && d < ro) {
|
|
519
|
+
float m = (d - ri) / (ro - ri);
|
|
520
|
+
add(o, max(d - ro, ri - 0.4 - d), 0.35 + 1.0 * sqrt(max(1.0 - (2.0 * m - 0.7) * (2.0 * m - 0.7), 0.0)), BRASS, uColor);
|
|
521
|
+
}
|
|
522
|
+
o.sd = min(o.sd, max(d - ro, ri - 0.4 - d));
|
|
523
|
+
} else if (uKind == 14) { // craft knife along x, blade at -L/2
|
|
524
|
+
float L = uLength;
|
|
525
|
+
float x = p.x + L * 0.5;
|
|
526
|
+
float body = sdBox(p - vec2(8.0, 0.0), vec2(L * 0.5 - 8.0, 4.8), 4.5);
|
|
527
|
+
float ay = abs(p.y);
|
|
528
|
+
if (body < 0.0) {
|
|
529
|
+
float hh = 4.8 + sqrt(max(4.8 * 4.8 - p.y * p.y, 0.0));
|
|
530
|
+
bool grip = x > 30.0 && x < 70.0;
|
|
531
|
+
bool collar = x > 16.0 && x < 24.0;
|
|
532
|
+
vec3 c = grip ? uColor : vec3(0.75, 0.77, 0.8);
|
|
533
|
+
if (grip) hh += 0.5 * sin(x * 1.6);
|
|
534
|
+
add(o, body, hh, grip ? MATTE : (collar ? METAL : STEEL), c);
|
|
535
|
+
}
|
|
536
|
+
// blade: angled triangle
|
|
537
|
+
vec2 b = vec2(x, p.y);
|
|
538
|
+
float blade = max(max(-b.x, b.x - 18.0), max(-b.y - 3.2, b.y - 3.2 + (18.0 - b.x) * 0.35));
|
|
539
|
+
if (x < 18.0 && blade < 0.0) add(o, blade, 1.2, METAL, vec3(0.92));
|
|
540
|
+
o.sd = min(o.sd, min(body, x < 18.0 ? blade : 1e5));
|
|
541
|
+
}
|
|
542
|
+
return o;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
float heightAt(vec2 p) { return scene(p).h; }
|
|
546
|
+
|
|
547
|
+
vec2 local(vec2 px) {
|
|
548
|
+
vec2 v = (px - uOrigin) / uScale;
|
|
549
|
+
float c = cos(uAngle), s = sin(uAngle);
|
|
550
|
+
return vec2(c * v.x + s * v.y, -s * v.x + c * v.y);
|
|
551
|
+
}
|
|
552
|
+
vec2 toLocalDir(vec2 dir) {
|
|
553
|
+
float c = cos(uAngle), s = sin(uAngle);
|
|
554
|
+
return vec2(c * dir.x + s * dir.y, -s * dir.x + c * dir.y);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
void main() {
|
|
558
|
+
vec2 px = gl_FragCoord.xy;
|
|
559
|
+
vec2 p = local(px);
|
|
560
|
+
Hit o = scene(p);
|
|
561
|
+
float alpha = clamp(0.5 - o.sd * uScale, 0.0, 1.0);
|
|
562
|
+
|
|
563
|
+
// light in the object's frame
|
|
564
|
+
vec2 l2 = toLocalDir(uLight.xy);
|
|
565
|
+
vec3 L = normalize(vec3(l2, uLight.z));
|
|
566
|
+
float tanE = L.z / max(length(L.xy), 1e-3);
|
|
567
|
+
vec2 ld = normalize(L.xy);
|
|
568
|
+
|
|
569
|
+
// shadow: march towards the light, compare the ray height with the field
|
|
570
|
+
float h0 = o.h * step(0.0, -o.sd);
|
|
571
|
+
float vis = 1.0;
|
|
572
|
+
float reach = (uMaxH - h0) / tanE + 2.0;
|
|
573
|
+
for (int i = 1; i <= 40; i++) {
|
|
574
|
+
float t = reach * float(i) / 40.0;
|
|
575
|
+
float z = h0 + t * tanE;
|
|
576
|
+
float hh = heightAt(p + ld * t);
|
|
577
|
+
if (hh > 0.001) vis = min(vis, clamp((z - hh) / (0.07 * t + 0.5), 0.0, 1.0));
|
|
578
|
+
if (vis <= 0.0) break;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
vec3 col = vec3(0.0);
|
|
582
|
+
if (alpha > 0.0) {
|
|
583
|
+
float e = 0.3;
|
|
584
|
+
float hx = (heightAt(p + vec2(e, 0.0)) - heightAt(p - vec2(e, 0.0))) / (2.0 * e);
|
|
585
|
+
float hy = (heightAt(p + vec2(0.0, e)) - heightAt(p - vec2(0.0, e))) / (2.0 * e);
|
|
586
|
+
vec3 n = normalize(vec3(-hx, -hy, 1.0));
|
|
587
|
+
vec3 V = vec3(0.0, 0.0, 1.0);
|
|
588
|
+
vec3 H = normalize(L + V);
|
|
589
|
+
float dif = max(dot(n, L), 0.0) * (0.35 + 0.65 * vis);
|
|
590
|
+
float nh = max(dot(n, H), 0.0);
|
|
591
|
+
vec3 r = reflect(-V, n);
|
|
592
|
+
vec3 base = o.col;
|
|
593
|
+
int m = o.mat;
|
|
594
|
+
if (m == METAL || m == BRASS || m == STEEL) {
|
|
595
|
+
vec3 tint = base;
|
|
596
|
+
float horizon = length(r.xy);
|
|
597
|
+
float env = 0.32 + 0.38 * r.z + 0.75 * max(dot(normalize(r.xy + 1e-5), ld), 0.0) * horizon;
|
|
598
|
+
if (m == STEEL) env += 0.08 * (vnoise(vec2(p.x * 0.08, p.y * 4.0)) - 0.5);
|
|
599
|
+
col = tint * env * (0.55 + 0.45 * vis) + vec3(1.0) * pow(nh, 90.0) * 1.4 * vis;
|
|
600
|
+
} else if (m == COFFEE) {
|
|
601
|
+
col = base * (0.6 + 0.5 * dif) + vec3(1.0) * pow(nh, 180.0) * 1.2 * vis;
|
|
602
|
+
} else {
|
|
603
|
+
float sp = m == PLASTIC ? 0.55 : (m == CERAMIC ? 0.45 : (m == PAINT ? 0.5 : (m == GRAPHITE ? 0.25 : 0.05)));
|
|
604
|
+
float sh = m == PLASTIC ? 70.0 : (m == CERAMIC ? 50.0 : 40.0);
|
|
605
|
+
if (m == WOOD) base *= 0.9 + 0.12 * sin(p.x * 0.9 + vnoise(p * vec2(0.15, 1.2)) * 6.0);
|
|
606
|
+
if (m == CARD) base *= 0.92 + 0.1 * vnoise(p * 0.8);
|
|
607
|
+
if (m == MATTE) base *= 0.95 + 0.06 * vnoise(p * 1.3);
|
|
608
|
+
col = base * (0.42 + 0.7 * dif) + vec3(1.0) * pow(nh, sh) * sp * vis;
|
|
609
|
+
// soft translucent glow for coloured plastic
|
|
610
|
+
if (m == PLASTIC) col += base * 0.12 * (1.0 - n.z);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// ground shadow under / around the object
|
|
614
|
+
float shadow = (1.0 - vis) * 0.42 * (1.0 - alpha);
|
|
615
|
+
float outA = alpha + shadow;
|
|
616
|
+
outColor = vec4(outA > 0.0 ? clamp(col, 0.0, 1.0) * alpha / outA : vec3(0.0), outA);
|
|
617
|
+
}
|
|
618
|
+
`
|
|
619
|
+
);
|
|
620
|
+
function Nt() {
|
|
621
|
+
let t = null, n;
|
|
622
|
+
const r = /* @__PURE__ */ new Map();
|
|
623
|
+
function o() {
|
|
624
|
+
const g = new OffscreenCanvas(1, 1).getContext("webgl2", { premultipliedAlpha: !1, antialias: !1, preserveDrawingBuffer: !0 });
|
|
625
|
+
if (!g) throw new Error("WebGL2 not available");
|
|
626
|
+
t = g;
|
|
627
|
+
const y = (z, C) => {
|
|
628
|
+
const d = g.createShader(z);
|
|
629
|
+
if (g.shaderSource(d, C), g.compileShader(d), !g.getShaderParameter(d, g.COMPILE_STATUS)) throw new Error(g.getShaderInfoLog(d) ?? "shader error");
|
|
630
|
+
return d;
|
|
631
|
+
};
|
|
632
|
+
if (n = g.createProgram(), g.attachShader(n, y(g.VERTEX_SHADER, $t)), g.attachShader(n, y(g.FRAGMENT_SHADER, It)), g.bindAttribLocation(n, 0, "aPos"), g.linkProgram(n), !g.getProgramParameter(n, g.LINK_STATUS)) throw new Error(g.getProgramInfoLog(n) ?? "link error");
|
|
633
|
+
const x = g.createVertexArray();
|
|
634
|
+
g.bindVertexArray(x);
|
|
635
|
+
const K = g.createBuffer();
|
|
636
|
+
g.bindBuffer(g.ARRAY_BUFFER, K), g.bufferData(g.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), g.STATIC_DRAW), g.enableVertexAttribArray(0), g.vertexAttribPointer(0, 2, g.FLOAT, !1, 0, 0);
|
|
637
|
+
}
|
|
638
|
+
const i = (s) => (r.has(s) || r.set(s, t.getUniformLocation(n, s)), r.get(s)), c = { azimuth: 225, elevation: 48 };
|
|
639
|
+
function m(s, g) {
|
|
640
|
+
t || o();
|
|
641
|
+
const y = t, [x, K] = pt[s.kind], z = s.length ?? (s.kind === "ruler" ? 300 : s.kind === "knife" ? 120 : 170), C = s.kind === "pencil" || s.kind === "ruler" || s.kind === "knife" ? z / 2 + 14 : x, d = (s.elevation ?? c.elevation) * Math.PI / 180, l = (s.azimuth ?? c.azimuth) * Math.PI / 180, w = K / Math.tan(d) + 3, k = -Math.cos(l) * w, I = -Math.sin(l) * w, L = -C + Math.min(0, k) - 2, P = C + Math.max(0, k) + 2, $ = -C + Math.min(0, I) - 2, F = C + Math.max(0, I) + 2, b = Math.ceil(P - L), R = Math.ceil(F - $), T = Math.ceil(b * g), O = Math.ceil(R * g);
|
|
642
|
+
y.canvas.width = T, y.canvas.height = O, y.viewport(0, 0, T, O), y.useProgram(n);
|
|
643
|
+
const B = tt(s.color ?? "#d33b3b"), D = tt(s.color2 ?? "#e8c79a");
|
|
644
|
+
y.uniform2f(i("uOrigin"), -L * g, -$ * g), y.uniform1f(i("uScale"), g), y.uniform1f(i("uAngle"), (s.angle ?? 0) * Math.PI / 180), y.uniform3f(i("uLight"), Math.cos(d) * Math.cos(l), Math.cos(d) * Math.sin(l), Math.sin(d)), y.uniform1i(i("uKind"), Pt[s.kind]), y.uniform3f(i("uColor"), B[0], B[1], B[2]), y.uniform3f(i("uColor2"), D[0], D[1], D[2]), y.uniform1f(i("uLength"), z), y.uniform1i(i("uPart"), s.part === "front" ? 1 : s.part === "back" ? 2 : 0), y.uniform1f(i("uSeed"), s.seed ?? 0), y.uniform1f(i("uMaxH"), K), y.drawArrays(y.TRIANGLES, 0, 3);
|
|
645
|
+
const q = new Uint8ClampedArray(T * O * 4);
|
|
646
|
+
y.readPixels(0, 0, T, O, y.RGBA, y.UNSIGNED_BYTE, q);
|
|
647
|
+
const e = document.createElement("canvas");
|
|
648
|
+
return e.width = T, e.height = O, e.getContext("2d").putImageData(new ImageData(q, T, O), 0, 0), e.style.width = `${b}px`, e.style.height = `${R}px`, s.kind === "ruler" && f(e, -L, -$, z, s.angle ?? 0, g), { canvas: e, w: b, h: R, ox: -L, oy: -$ };
|
|
649
|
+
}
|
|
650
|
+
function f(s, g, y, x, K, z) {
|
|
651
|
+
const C = s.getContext("2d");
|
|
652
|
+
C.save(), C.scale(z, z), C.translate(g, y), C.rotate(K * Math.PI / 180), C.font = "600 7.5px 'IBM Plex Mono', monospace", C.fillStyle = "rgba(30,30,32,0.85)", C.textAlign = "center";
|
|
653
|
+
const d = 3.7795;
|
|
654
|
+
for (let l = 1; l * 10 * d < x - 18; l++) C.fillText(String(l), -x / 2 + 8 + l * 10 * d, -1);
|
|
655
|
+
C.font = "500 6px 'IBM Plex Mono', monospace", C.textAlign = "left", C.fillText("STAINLESS · 30 cm", -x / 2 + 10, 9), C.restore();
|
|
656
|
+
}
|
|
657
|
+
return { renderProp: m, dispose() {
|
|
658
|
+
t?.getExtension("WEBGL_lose_context")?.loseContext(), t = null, r.clear();
|
|
659
|
+
} };
|
|
660
|
+
}
|
|
661
|
+
function zt(t) {
|
|
662
|
+
const n = Nt(), r = Lt(), o = t.light, i = () => t.renderer, { renderProp: c } = n, { renderCord: m } = r, { preloadPhotos: f, renderPhoto: s, runPhotos: g } = t.photos, y = () => t.pixelRatio?.() ?? Math.min(2, window.devicePixelRatio || 1), x = { x: 1.5, y: 4 }, K = 9, z = (e, a, p) => {
|
|
663
|
+
const u = p * Math.PI / 180, h = Math.cos(u), v = Math.sin(u);
|
|
664
|
+
return [h * e - v * a, v * e + h * a];
|
|
665
|
+
};
|
|
666
|
+
function C() {
|
|
667
|
+
const [e, a] = z(x.x, x.y, o.azimuth - 225), p = Math.hypot(e, a) || 1;
|
|
668
|
+
return [e / p, a / p];
|
|
669
|
+
}
|
|
670
|
+
function d(e, a) {
|
|
671
|
+
const p = a ? gt(st(e), a) : st(e), u = t.grain();
|
|
672
|
+
return {
|
|
673
|
+
...p,
|
|
674
|
+
scale: p.scale * u,
|
|
675
|
+
fibers: { ...p.fibers, density: p.fibers.density / u },
|
|
676
|
+
specks: { ...p.specks, density: p.specks.density / u }
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function l(e, a, p, u, h) {
|
|
680
|
+
const v = document.createElement("canvas");
|
|
681
|
+
return v.className = h, v.width = a, v.height = p, v.getContext("2d").putImageData(new ImageData(e, a, p), 0, 0), v.style.width = `${a / u}px`, v.style.height = `${p / u}px`, v;
|
|
682
|
+
}
|
|
683
|
+
function w(e, a, p, u) {
|
|
684
|
+
const h = Math.ceil(p * 2), v = document.createElement("canvas");
|
|
685
|
+
v.className = u, v.width = e.width + 2 * h * a, v.height = e.height + 2 * h * a;
|
|
686
|
+
const M = v.getContext("2d");
|
|
687
|
+
return M.filter = `blur(${p * a}px)`, M.drawImage(e, h * a, h * a), M.filter = "none", M.globalCompositeOperation = "source-in", M.fillStyle = "#000", M.fillRect(0, 0, v.width, v.height), v.style.width = `${v.width / a}px`, v.style.height = `${v.height / a}px`, { canvas: v, pad: h };
|
|
688
|
+
}
|
|
689
|
+
function k(e, a, p, u, h, v) {
|
|
690
|
+
const M = d(e, a);
|
|
691
|
+
M.lighting = { ...M.lighting, azimuth: o.azimuth - v, elevation: o.elevation };
|
|
692
|
+
const S = p.shadow === !1 ? !1 : { ...p.shadow ?? {} };
|
|
693
|
+
if (S) {
|
|
694
|
+
const A = Math.tan((90 - o.elevation) * Math.PI / 180) / Math.tan(42 * Math.PI / 180), _ = (S.offsetX ?? x.x) * A, W = (S.offsetY ?? x.y) * A, [V, N] = z(_, W, o.azimuth - 225);
|
|
695
|
+
[S.offsetX, S.offsetY] = z(V, N, -v);
|
|
696
|
+
}
|
|
697
|
+
const E = y();
|
|
698
|
+
return { params: M, spec: { ...p, width: u, height: h, pixelRatio: E, shadow: S }, k: E };
|
|
699
|
+
}
|
|
700
|
+
function I(e, a, p, u, h, v, M) {
|
|
701
|
+
const { params: S, spec: E, k: A } = k(e, a, p, u, h, v);
|
|
702
|
+
return { res: i().renderSheet(S, E, M), k: A };
|
|
703
|
+
}
|
|
704
|
+
const L = ["pushpin", "mappin", "thumbtack", "staple"], P = ["magnet", "magnet-neo", "magnet-bar"], $ = ["binder", "paperclip"];
|
|
705
|
+
function F(e) {
|
|
706
|
+
return e.startsWith("card") || e === "chipboard" || e === "polaroid" ? "thick" : ["napkin", "tracing", "thermal", "newsprint", "newsprint-old", "clear-tape", "washi", "masking"].includes(e) ? "thin" : "normal";
|
|
707
|
+
}
|
|
708
|
+
function b(e, a) {
|
|
709
|
+
const p = (E) => e.attach.filter((A) => !A.binds && E(A)).map((A) => {
|
|
710
|
+
const _ = a.get(A);
|
|
711
|
+
return A.type === "sheet" ? { x: A.x, y: A.y, el: _, angle: A.angle, len: A.w } : A.type === "prop" ? { x: A.x, y: A.y, el: _ } : A.type === "photo" ? { x: A.cx, y: A.cy, el: _ } : { x: e.w / 2, y: 28, el: _ };
|
|
712
|
+
}), u = p((E) => E.type === "prop" && L.includes(E.prop.kind));
|
|
713
|
+
if (u.length) return { kind: "pinned", points: u };
|
|
714
|
+
const h = p((E) => E.type === "prop" && P.includes(E.prop.kind));
|
|
715
|
+
if (h.length) return { kind: "magnetic", points: h };
|
|
716
|
+
const v = p((E) => E.type === "sheet" && E.label.endsWith("tape"));
|
|
717
|
+
if (v.length) return { kind: "taped", points: v };
|
|
718
|
+
const M = p((E) => E.type === "prop" && $.includes(E.prop.kind) && E.layer === "front");
|
|
719
|
+
if (M.length) return { kind: "hung", points: M };
|
|
720
|
+
const S = p((E) => E.type === "cord");
|
|
721
|
+
return S.length ? { kind: "hung", points: S } : e.paper === "sticky" ? { kind: "adhesive", points: [{ x: e.w / 2, y: e.h * 0.09 }] } : { kind: "free", points: [] };
|
|
722
|
+
}
|
|
723
|
+
function R(e) {
|
|
724
|
+
const a = document.createElement("div");
|
|
725
|
+
a.className = "card pc-card", a.style.width = `${e.w}px`, a.style.height = `${e.h}px`;
|
|
726
|
+
const p = document.createElement("div");
|
|
727
|
+
p.className = "shadow";
|
|
728
|
+
const u = document.createElement("div");
|
|
729
|
+
u.className = "back";
|
|
730
|
+
const h = document.createElement("div");
|
|
731
|
+
h.className = "paper";
|
|
732
|
+
const v = document.createElement("div");
|
|
733
|
+
v.className = "front";
|
|
734
|
+
const M = document.createElement("div");
|
|
735
|
+
M.className = "sheen", a.append(p, u, h, v);
|
|
736
|
+
const S = { el: a, paper: h, shadow: p, sheen: M, holder: { kind: "free", points: [] }, thickness: F(e.paper), copy: null, litFor: null, render: async () => {
|
|
737
|
+
} };
|
|
738
|
+
let E = !1;
|
|
739
|
+
return S.render = async (A = e.rot) => {
|
|
740
|
+
h.replaceChildren(), u.replaceChildren(), v.replaceChildren(), p.replaceChildren(), S.copy = null, S.litFor = A, e.ink && await St(e.ink), E || (t.onPrepared?.(e, S, A), E = !0), e.ink && await g(e.ink), await f(e.attach.flatMap((G) => G.type === "photo" ? [G.photo] : []));
|
|
741
|
+
const { params: _, spec: W, k: V } = k(e.paper, e.params, e.look, e.w, e.h, A), N = xt(i(), _, W), H = N.shadow;
|
|
742
|
+
H.className = "contact";
|
|
743
|
+
const U = w(H, V, K, "lifted");
|
|
744
|
+
U.canvas.style.left = `${parseFloat(H.style.left) - U.pad}px`, U.canvas.style.top = `${parseFloat(H.style.top) - U.pad}px`, p.append(H, U.canvas), N.base.className = "base", N.shade.className = "shade", h.append(N.base), e.ink && h.append(e.ink), h.append(N.shade, M);
|
|
745
|
+
for (const G of [e.ink, M])
|
|
746
|
+
G && Object.assign(G.style, { maskImage: N.mask, webkitMaskImage: N.mask, maskPosition: N.maskPosition, maskSize: N.maskSize, maskRepeat: "no-repeat" });
|
|
747
|
+
const X = /* @__PURE__ */ new Map();
|
|
748
|
+
for (const G of e.attach) {
|
|
749
|
+
const j = T(G, A, G.type === "cord" ? v : G.layer === "back" ? u : v, u);
|
|
750
|
+
j && X.set(G, j);
|
|
751
|
+
}
|
|
752
|
+
S.holder = b(e, X), a.classList.add("ready");
|
|
753
|
+
}, S;
|
|
754
|
+
}
|
|
755
|
+
function T(e, a, p, u) {
|
|
756
|
+
if (e.type === "cord") {
|
|
757
|
+
let S = null;
|
|
758
|
+
for (const E of ["back", "front"]) {
|
|
759
|
+
const A = m(e.spec, E, y(), o.azimuth - a, o.elevation);
|
|
760
|
+
A && (A.canvas.className = "prop-sprite", A.canvas.style.left = `${-A.ox}px`, A.canvas.style.top = `${-A.oy}px`, (E === "back" ? u : p).appendChild(A.canvas), E === "front" && (S = A.canvas));
|
|
761
|
+
}
|
|
762
|
+
return S;
|
|
763
|
+
}
|
|
764
|
+
if (e.type === "html")
|
|
765
|
+
return p.appendChild(e.el), e.el;
|
|
766
|
+
if (e.type === "prop") {
|
|
767
|
+
const S = c({ ...e.prop, azimuth: o.azimuth - a, elevation: o.elevation }, y());
|
|
768
|
+
return S.canvas.className = "prop-sprite", S.canvas.style.left = `${e.x - S.ox}px`, S.canvas.style.top = `${e.y - S.oy}px`, S.canvas.style.transformOrigin = `${S.ox}px ${S.oy}px`, p.appendChild(S.canvas), S.canvas;
|
|
769
|
+
}
|
|
770
|
+
if (e.type === "photo") return O(e, a, p);
|
|
771
|
+
const { res: h, k: v } = I(e.paper, e.params, e.look, e.w, e.h, a + e.angle, ["color"]), M = l(h.maps.color.data, h.width, h.height, v, "tape");
|
|
772
|
+
return M.style.left = `${e.x - h.width / v / 2}px`, M.style.top = `${e.y - h.height / v / 2}px`, M.style.transform = `rotate(${e.angle}deg)`, p.appendChild(M), M;
|
|
773
|
+
}
|
|
774
|
+
function O(e, a, p) {
|
|
775
|
+
const { res: u, k: h } = I(e.paper, e.params, e.look, e.w, e.h, a + e.angle, ["base", "shade", "shadow"]), v = u.margin, M = l(u.maps.base.data, u.width, u.height, h, ""), S = l(u.maps.shade.data, u.width, u.height, h, ""), E = document.createElement("canvas");
|
|
776
|
+
E.width = u.width, E.height = u.height;
|
|
777
|
+
const A = E.getContext("2d");
|
|
778
|
+
A.drawImage(M, 0, 0);
|
|
779
|
+
const _ = s(e.photo, e.window.w, e.window.h, h);
|
|
780
|
+
A.globalCompositeOperation = "source-atop", A.drawImage(_, v + e.window.x * h, v + e.window.y * h), A.globalCompositeOperation = "hard-light", A.drawImage(S, 0, 0), A.globalCompositeOperation = "destination-in", A.drawImage(M, 0, 0);
|
|
781
|
+
const W = l(u.maps.shadow.data, u.width, u.height, h, "tape photo-shadow"), V = (N) => (N.style.left = `${e.cx - u.width / h / 2}px`, N.style.top = `${e.cy - u.height / h / 2}px`, N.style.width = `${u.width / h}px`, N.style.height = `${u.height / h}px`, N.style.transform = `rotate(${e.angle}deg)`, N);
|
|
782
|
+
return E.className = "tape photo-print", p.append(V(W), V(E)), E;
|
|
783
|
+
}
|
|
784
|
+
function B(e) {
|
|
785
|
+
const a = c({ ...e, azimuth: o.azimuth, elevation: o.elevation }, y()), p = document.createElement("div");
|
|
786
|
+
p.className = "prop", p.style.width = `${a.w}px`, p.style.height = `${a.h}px`, a.canvas.className = "prop-sprite";
|
|
787
|
+
const u = y(), h = w(a.canvas, u, 7, "lifted"), v = document.createElement("div");
|
|
788
|
+
return v.className = "shadow", h.canvas.style.left = `${-h.pad}px`, h.canvas.style.top = `${-h.pad}px`, v.appendChild(h.canvas), p.append(v, a.canvas, q(a.canvas, a.w, a.h)), { el: p, ox: a.ox, oy: a.oy, w: a.w, h: a.h, shadow: v, height: pt[e.kind][1] };
|
|
789
|
+
}
|
|
790
|
+
const D = 2;
|
|
791
|
+
function q(e, a, p) {
|
|
792
|
+
const { data: u } = e.getContext("2d").getImageData(0, 0, e.width, e.height), h = e.width / a, v = e.height / p, M = Math.ceil(a / D), S = Math.ceil(p / D), E = new Uint8Array(M * S);
|
|
793
|
+
for (let N = 0; N < e.height; N++)
|
|
794
|
+
for (let H = 0; H < e.width; H++)
|
|
795
|
+
u[(N * e.width + H) * 4 + 3] > 128 && (E[Math.floor(N / v / D) * M + Math.floor(H / h / D)] = 1);
|
|
796
|
+
const A = (N, H) => {
|
|
797
|
+
for (let U = -1; U <= 1; U++)
|
|
798
|
+
for (let X = -1; X <= 1; X++) {
|
|
799
|
+
const G = N + X, j = H + U;
|
|
800
|
+
if (G >= 0 && j >= 0 && G < M && j < S && E[j * M + G]) return !0;
|
|
801
|
+
}
|
|
802
|
+
return !1;
|
|
803
|
+
};
|
|
804
|
+
let _ = "";
|
|
805
|
+
for (let N = 0; N < S; N++)
|
|
806
|
+
for (let H = 0; H < M; H++) {
|
|
807
|
+
if (!A(H, N)) continue;
|
|
808
|
+
const U = H;
|
|
809
|
+
for (; H + 1 < M && A(H + 1, N); ) H++;
|
|
810
|
+
_ += `M${U * D} ${N * D}h${(H - U + 1) * D}v${D}h${-(H - U + 1) * D}z`;
|
|
811
|
+
}
|
|
812
|
+
const W = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
813
|
+
W.setAttribute("class", "prop-hit"), W.setAttribute("width", `${a}`), W.setAttribute("height", `${p}`);
|
|
814
|
+
const V = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
815
|
+
return V.setAttribute("d", _), W.appendChild(V), W;
|
|
816
|
+
}
|
|
817
|
+
return { buildCard: R, buildProp: B, shadowDir: C, grainParams: d, dpr: y, dispose() {
|
|
818
|
+
n.dispose(), r.dispose();
|
|
819
|
+
} };
|
|
820
|
+
}
|
|
821
|
+
const Y = /* @__PURE__ */ new WeakMap();
|
|
822
|
+
function Ot(t, n, r = () => Math.min(2, devicePixelRatio || 1)) {
|
|
823
|
+
const o = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map(), c = t;
|
|
824
|
+
async function m(d) {
|
|
825
|
+
let l = i.get(d);
|
|
826
|
+
return l || (l = (async () => {
|
|
827
|
+
const w = new Image();
|
|
828
|
+
return w.crossOrigin = "anonymous", w.src = await n(d), await w.decode(), o.set(d, w), w;
|
|
829
|
+
})().catch((w) => {
|
|
830
|
+
throw i.delete(d), new Error(`Unable to load photo ${d}: ${w}`);
|
|
831
|
+
}), i.set(d, l)), l;
|
|
832
|
+
}
|
|
833
|
+
const f = {
|
|
834
|
+
none: "contrast(1.04) saturate(1.04)",
|
|
835
|
+
bw: "grayscale(1) contrast(1.18) brightness(1.03)",
|
|
836
|
+
sepia: "sepia(0.85) contrast(1.06) brightness(0.98) saturate(0.9)",
|
|
837
|
+
faded: "contrast(0.82) saturate(0.68) brightness(1.06)",
|
|
838
|
+
instant: "contrast(0.9) saturate(0.85) brightness(1.04)",
|
|
839
|
+
warm: "sepia(0.22) saturate(1.18) contrast(1.05)",
|
|
840
|
+
expired: "contrast(0.86) saturate(1.3) hue-rotate(-10deg) brightness(1.04)"
|
|
841
|
+
};
|
|
842
|
+
let s = null;
|
|
843
|
+
function g() {
|
|
844
|
+
if (s) return s;
|
|
845
|
+
const d = document.createElement("canvas");
|
|
846
|
+
d.width = d.height = 128;
|
|
847
|
+
const l = d.getContext("2d", { willReadFrequently: !0 }), w = l.createImageData(128, 128);
|
|
848
|
+
let k = 12345;
|
|
849
|
+
for (let I = 0; I < w.data.length; I += 4) {
|
|
850
|
+
k = k * 1103515245 + 12345 >>> 0;
|
|
851
|
+
const L = 128 + ((k >>> 16 & 255) - 128) * 0.9;
|
|
852
|
+
w.data[I] = w.data[I + 1] = w.data[I + 2] = L, w.data[I + 3] = 255;
|
|
853
|
+
}
|
|
854
|
+
return l.putImageData(w, 0, 0), s = d;
|
|
855
|
+
}
|
|
856
|
+
const y = (d, l) => {
|
|
857
|
+
const w = Math.sin(d * 12.9898 + l * 78.233) * 43758.5453;
|
|
858
|
+
return w - Math.floor(w);
|
|
859
|
+
};
|
|
860
|
+
function x(d, l, w, k) {
|
|
861
|
+
const I = d.getImageData(0, 0, l, w), L = I.data, P = 3.4 * k, $ = [0.26, 1.31, 0.79].map((b) => [Math.cos(b), Math.sin(b)]), F = Math.PI * 2 / P;
|
|
862
|
+
for (let b = 0; b < w; b++)
|
|
863
|
+
for (let R = 0; R < l; R++) {
|
|
864
|
+
const T = (b * l + R) * 4;
|
|
865
|
+
for (let O = 0; O < 3; O++) {
|
|
866
|
+
const [B, D] = $[O], q = (R * B - b * D) * F, e = (R * D + b * B) * F, a = 0.5 + 0.25 * (Math.cos(q) + Math.cos(e)), p = L[T + O] / 255, h = 1 - Math.min(1, Math.max(0, (1 - p - a) * 3 + 0.5));
|
|
867
|
+
L[T + O] = Math.round((p * 0.45 + h * 0.55) * 255);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
d.putImageData(I, 0, 0);
|
|
871
|
+
}
|
|
872
|
+
function K(d, l, w, k) {
|
|
873
|
+
const I = c(d.id), L = o.get(d.id);
|
|
874
|
+
if (!L) throw new Error(`Photo not loaded: ${d.id}`);
|
|
875
|
+
const P = Math.max(1, Math.round(l * k)), $ = Math.max(1, Math.round(w * k)), F = document.createElement("canvas");
|
|
876
|
+
F.width = P, F.height = $;
|
|
877
|
+
const b = F.getContext("2d", { willReadFrequently: !0 }), R = L instanceof HTMLImageElement ? L.naturalWidth : L.width, T = L instanceof HTMLImageElement ? L.naturalHeight : L.height, O = Math.max(P / R, $ / T) * (d.zoom ?? 1), B = P / O, D = $ / O, q = Math.min(R - B, Math.max(0, I.focus[0] * R - B / 2)), e = Math.min(T - D, Math.max(0, I.focus[1] * T - D / 2)), a = d.grade ?? "none", p = d.seed ?? 1;
|
|
878
|
+
b.filter = f[a], b.drawImage(L, q, e, B, D, 0, 0, P, $), b.filter = "none";
|
|
879
|
+
const u = (h, v, M = 1) => {
|
|
880
|
+
b.globalCompositeOperation = h, b.globalAlpha = M, b.fillStyle = v, b.fillRect(0, 0, P, $), b.globalAlpha = 1, b.globalCompositeOperation = "source-over";
|
|
881
|
+
};
|
|
882
|
+
if (a === "faded" && u("lighten", "rgb(46,40,52)"), a === "sepia" && u("lighten", "rgb(52,40,30)"), a === "instant" && (u("lighten", "rgb(34,50,54)"), u("soft-light", "#ffd9a0", 0.35)), a === "expired" && (u("lighten", "rgb(40,30,46)"), u("soft-light", y(p, 1) < 0.5 ? "#ff6fa0" : "#6fffb0", 0.3)), d.leak) {
|
|
883
|
+
const h = Math.floor(y(p, 2) * 4), v = h === 0 ? 0 : h === 1 ? P : P * y(p, 3), M = h === 2 ? 0 : h === 3 ? $ : $ * y(p, 4), S = Math.max(P, $) * (0.45 + 0.3 * y(p, 5)), E = b.createRadialGradient(v, M, 0, v, M, S);
|
|
884
|
+
E.addColorStop(0, "rgba(255,150,60,0.85)"), E.addColorStop(0.35, "rgba(255,90,40,0.4)"), E.addColorStop(1, "rgba(255,60,30,0)"), u("screen", E);
|
|
885
|
+
}
|
|
886
|
+
if (d.halftone && x(b, P, $, k), d.vignette) {
|
|
887
|
+
const h = b.createRadialGradient(P / 2, $ / 2, Math.min(P, $) * 0.3, P / 2, $ / 2, Math.hypot(P, $) * 0.56);
|
|
888
|
+
h.addColorStop(0, "rgba(0,0,0,0)"), h.addColorStop(1, `rgba(0,0,0,${d.vignette.toFixed(2)})`), u("multiply", h);
|
|
889
|
+
}
|
|
890
|
+
if (d.grain) {
|
|
891
|
+
const h = b.createPattern(g(), "repeat");
|
|
892
|
+
h.setTransform(new DOMMatrix().translate(y(p, 6) * 128, y(p, 7) * 128).scale(k * 0.6)), u("overlay", h, 0.18 * d.grain);
|
|
893
|
+
}
|
|
894
|
+
if (d.stamp) {
|
|
895
|
+
const h = Math.max(9, Math.min(l, w) * 0.075) * k;
|
|
896
|
+
b.font = `${h}px VT323, monospace`, b.textAlign = "right", b.textBaseline = "alphabetic", b.shadowColor = "rgba(255,120,30,0.9)", b.shadowBlur = h * 0.35, b.fillStyle = "rgba(255,150,60,0.95)", b.fillText(d.stamp, P - h * 0.9, $ - h * 0.7), b.shadowBlur = 0;
|
|
897
|
+
}
|
|
898
|
+
return F;
|
|
899
|
+
}
|
|
900
|
+
async function z(d) {
|
|
901
|
+
await Promise.all([...d.map((l) => m(l.id)), ...d.some((l) => l.stamp) ? [document.fonts.load("16px VT323")] : []]);
|
|
902
|
+
}
|
|
903
|
+
async function C(d) {
|
|
904
|
+
const l = [...d.querySelectorAll("canvas.photo")].filter((w) => Y.has(w));
|
|
905
|
+
await z(l.map((w) => Y.get(w)));
|
|
906
|
+
for (const w of l) {
|
|
907
|
+
const k = K(Y.get(w), parseFloat(w.style.width), parseFloat(w.style.height), r());
|
|
908
|
+
w.width = k.width, w.height = k.height, w.getContext("2d").drawImage(k, 0, 0);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return { renderPhoto: K, preloadPhotos: z, runPhotos: C, loadPhoto: m, dispose() {
|
|
912
|
+
i.clear(), o.clear(), s = null;
|
|
913
|
+
} };
|
|
914
|
+
}
|
|
915
|
+
const rt = new Set("div span b i em strong s u small sub sup br p h1 h2 h3 ul ol li img canvas svg g path rect circle ellipse line polyline polygon text tspan defs clipPath mask pattern linearGradient radialGradient stop filter feTurbulence feDisplacementMap feColorMatrix feComposite title".split(" ")), Kt = new Set("class style id width height viewBox d fill stroke stroke-width stroke-linecap stroke-linejoin stroke-dasharray stroke-dashoffset fill-rule clip-rule opacity transform x y x1 y1 x2 y2 cx cy r rx ry points offset stop-color stop-opacity gradientUnits gradientTransform patternUnits patternTransform preserveAspectRatio xmlns filter clip-path mask font-size font-family text-anchor dominant-baseline type baseFrequency numOctaves seed result in in2 scale xChannelSelector yChannelSelector values operator aria-hidden aria-label role".split(" "));
|
|
916
|
+
function ut(t = {}) {
|
|
917
|
+
const n = {};
|
|
918
|
+
for (const [r, o] of Object.entries(t))
|
|
919
|
+
!Kt.has(r) || /(?:javascript:|expression\s*\(|@import|behavior\s*:)/i.test(o) || [...o.matchAll(/url\(([^)]*)\)/gi)].some((i) => !/^['"]?#[\w-]+['"]?$/.test(i[1].trim())) || (n[r] = o);
|
|
920
|
+
return n;
|
|
921
|
+
}
|
|
922
|
+
function it(t, n = "n", r = { n: 0 }) {
|
|
923
|
+
const o = `${n}-${r.n++}`;
|
|
924
|
+
if (t.nodeType === Node.TEXT_NODE) return { id: o, text: t.textContent ?? "" };
|
|
925
|
+
if (!(t instanceof Element) || !rt.has(t.localName)) throw new Error(`Unsupported composition element ${t.nodeName}`);
|
|
926
|
+
t.setAttribute("data-pc-node", o);
|
|
927
|
+
const i = ut(Object.fromEntries([...t.attributes].filter((f) => f.name !== "data-pc-node").map((f) => [f.name, f.value]))), c = at.get(t), m = Y.get(t);
|
|
928
|
+
return {
|
|
929
|
+
id: o,
|
|
930
|
+
tag: t.localName,
|
|
931
|
+
...t.namespaceURI?.includes("svg") ? { namespace: "svg" } : {},
|
|
932
|
+
attributes: i,
|
|
933
|
+
children: [...t.childNodes].filter((f) => f.nodeType === Node.ELEMENT_NODE || f.nodeType === Node.TEXT_NODE).map((f) => it(f, n, r)),
|
|
934
|
+
...c ? { fit: { spec: structuredClone(c), ...t.hasAttribute("data-fit") ? { resolved: JSON.stringify(c) } : {} } } : {},
|
|
935
|
+
...m ? { photo: structuredClone(m) } : {}
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function Dt(t) {
|
|
939
|
+
const n = structuredClone(t);
|
|
940
|
+
for (const r of n.blocks) if (r.html) {
|
|
941
|
+
const o = document.createElement("template");
|
|
942
|
+
o.innerHTML = r.html;
|
|
943
|
+
const i = document.createElement("div");
|
|
944
|
+
for (const c of o.content.childNodes)
|
|
945
|
+
(c.nodeType === Node.TEXT_NODE || c instanceof Element && rt.has(c.localName)) && i.append(J(it(c), "fit"));
|
|
946
|
+
r.html = i.innerHTML;
|
|
947
|
+
}
|
|
948
|
+
return n;
|
|
949
|
+
}
|
|
950
|
+
function J(t, n) {
|
|
951
|
+
if (!t.tag) return document.createTextNode(t.text ?? "");
|
|
952
|
+
const r = n ? `${n}-` : "";
|
|
953
|
+
if (!rt.has(t.tag)) throw new Error(`Unsupported composition tag ${t.tag}`);
|
|
954
|
+
const o = t.namespace === "svg" ? document.createElementNS("http://www.w3.org/2000/svg", t.tag) : document.createElement(t.tag);
|
|
955
|
+
for (const [i, c] of Object.entries(ut(t.attributes)))
|
|
956
|
+
o.setAttribute(i, i === "id" ? `${r}${c}` : c.replace(/url\(['"]?#([\w-]+)['"]?\)/g, `url(#${r}$1)`));
|
|
957
|
+
o.setAttribute("data-pc-node", t.id);
|
|
958
|
+
for (const i of t.children ?? []) o.appendChild(J(i, n));
|
|
959
|
+
if (t.photo && Y.set(o, structuredClone(t.photo)), t.fit) {
|
|
960
|
+
const i = Dt(t.fit.spec);
|
|
961
|
+
at.set(o, i), t.fit.resolved !== JSON.stringify(t.fit.spec) && At(o, i);
|
|
962
|
+
}
|
|
963
|
+
return o;
|
|
964
|
+
}
|
|
965
|
+
const et = /* @__PURE__ */ new Map();
|
|
966
|
+
async function Ft(t, n, r) {
|
|
967
|
+
const o = /* @__PURE__ */ new Map(), i = [];
|
|
968
|
+
let c = !1;
|
|
969
|
+
const m = () => {
|
|
970
|
+
c || (c = !0, i.forEach((f) => f()));
|
|
971
|
+
};
|
|
972
|
+
try {
|
|
973
|
+
for (const s of t) o.set(s.id, await n(s));
|
|
974
|
+
await Promise.all(t.filter((s) => s.kind === "font").map(async (s) => {
|
|
975
|
+
const g = o.get(s.id);
|
|
976
|
+
if (!s.family) throw new Error(`Missing font family for ${s.id}`);
|
|
977
|
+
const y = JSON.stringify([s.family, s.descriptors, g]);
|
|
978
|
+
let x = et.get(y);
|
|
979
|
+
if (!x) {
|
|
980
|
+
const z = new FontFace(s.family, `url(${JSON.stringify(g)})`, s.descriptors);
|
|
981
|
+
x = { count: 0, face: z, ready: z.load() }, et.set(y, x);
|
|
982
|
+
}
|
|
983
|
+
x.count++;
|
|
984
|
+
const K = x;
|
|
985
|
+
i.push(() => {
|
|
986
|
+
--K.count === 0 && (document.fonts.delete(K.face), et.delete(y));
|
|
987
|
+
}), await K.ready, c || document.fonts.add(K.face);
|
|
988
|
+
}));
|
|
989
|
+
let f;
|
|
990
|
+
if (r) {
|
|
991
|
+
const s = t.filter((g) => g.kind === "pen");
|
|
992
|
+
if (s.length) {
|
|
993
|
+
const { TegakiEngine: g } = await import("tegaki/core"), y = /* @__PURE__ */ new Map();
|
|
994
|
+
for (const x of s) {
|
|
995
|
+
const K = await fetch(o.get(x.id));
|
|
996
|
+
if (!K.ok) throw new Error(`Unable to load pen data ${x.id}`);
|
|
997
|
+
const z = await K.json(), C = t.find((l) => l.kind === "font" && l.family === x.family);
|
|
998
|
+
if (!C) throw new Error(`Missing pen font ${x.family}`);
|
|
999
|
+
const d = { ...z, fontUrl: o.get(C.id), fontFaceCSS: void 0 };
|
|
1000
|
+
await g.preload(d), y.set(d.family.toLowerCase(), d), x.matches && y.set(x.matches.toLowerCase(), d);
|
|
1001
|
+
}
|
|
1002
|
+
f = { Engine: g, bundleFor: (x) => y.get(x.split(",")[0].trim().replace(/^['"]|['"]$/g, "").toLowerCase()) };
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return { urls: o, penContext: f, dispose: m };
|
|
1006
|
+
} catch (f) {
|
|
1007
|
+
throw m(), f;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const Ht = ` <svg width="0" height="0" style="position: absolute" aria-hidden="true">
|
|
1011
|
+
<filter id="f-ink" x="-5%" y="-5%" width="110%" height="110%">
|
|
1012
|
+
<feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2" seed="4" result="n" />
|
|
1013
|
+
<feDisplacementMap in="SourceGraphic" in2="n" scale="1.4" xChannelSelector="R" yChannelSelector="G" />
|
|
1014
|
+
</filter>
|
|
1015
|
+
<filter id="f-pencil" x="-5%" y="-5%" width="110%" height="110%">
|
|
1016
|
+
<feTurbulence type="fractalNoise" baseFrequency="1.3" numOctaves="2" seed="9" result="n" />
|
|
1017
|
+
<feColorMatrix in="n" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2.4 0 0 0 -0.55" result="m" />
|
|
1018
|
+
<feComposite in="SourceGraphic" in2="m" operator="in" result="g" />
|
|
1019
|
+
<feDisplacementMap in="g" in2="n" scale="1" xChannelSelector="G" yChannelSelector="B" />
|
|
1020
|
+
</filter>
|
|
1021
|
+
<filter id="f-stamp" x="-5%" y="-10%" width="110%" height="120%">
|
|
1022
|
+
<feTurbulence type="fractalNoise" baseFrequency="0.55" numOctaves="3" seed="2" result="n" />
|
|
1023
|
+
<feColorMatrix in="n" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3.2 0 0 0 -0.75" result="m" />
|
|
1024
|
+
<feComposite in="SourceGraphic" in2="m" operator="in" result="g" />
|
|
1025
|
+
<feDisplacementMap in="g" in2="n" scale="1.6" xChannelSelector="R" yChannelSelector="G" />
|
|
1026
|
+
</filter>
|
|
1027
|
+
<filter id="f-wash" x="-10%" y="-10%" width="120%" height="120%">
|
|
1028
|
+
<feTurbulence type="fractalNoise" baseFrequency="0.035" numOctaves="3" seed="7" result="n" />
|
|
1029
|
+
<feDisplacementMap in="SourceGraphic" in2="n" scale="16" xChannelSelector="R" yChannelSelector="G" result="d" />
|
|
1030
|
+
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="5" result="g" />
|
|
1031
|
+
<feColorMatrix in="g" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.2 0 0 0 0.25" result="ga" />
|
|
1032
|
+
<feComposite in="d" in2="ga" operator="in" />
|
|
1033
|
+
</filter>
|
|
1034
|
+
<filter id="f-stain" x="-10%" y="-10%" width="120%" height="120%">
|
|
1035
|
+
<feTurbulence type="fractalNoise" baseFrequency="0.06" numOctaves="3" seed="11" result="n" />
|
|
1036
|
+
<feDisplacementMap in="SourceGraphic" in2="n" scale="7" xChannelSelector="R" yChannelSelector="G" />
|
|
1037
|
+
</filter>
|
|
1038
|
+
</svg>`;
|
|
1039
|
+
function mt(t) {
|
|
1040
|
+
let n = t >>> 0 || 2654435769;
|
|
1041
|
+
const r = () => {
|
|
1042
|
+
n = n + 1831565813 >>> 0;
|
|
1043
|
+
let i = n;
|
|
1044
|
+
return i = Math.imul(i ^ i >>> 15, i | 1), i ^= i + Math.imul(i ^ i >>> 7, i | 61), ((i ^ i >>> 14) >>> 0) / 4294967296;
|
|
1045
|
+
}, o = r;
|
|
1046
|
+
return o.range = (i, c) => i + (c - i) * r(), o.int = (i, c) => Math.floor(i + (c - i + 1) * r()), o.pick = (i) => i[Math.floor(r() * i.length)], o.chance = (i) => r() < i, o.weighted = (i, c) => {
|
|
1047
|
+
const m = i.reduce((s, g) => s + c(g), 0);
|
|
1048
|
+
let f = r() * m;
|
|
1049
|
+
for (const s of i) if ((f -= c(s)) < 0) return s;
|
|
1050
|
+
return i[i.length - 1];
|
|
1051
|
+
}, o.fork = () => mt(Math.floor(r() * 4294967296)), o;
|
|
1052
|
+
}
|
|
1053
|
+
function nt(t, n) {
|
|
1054
|
+
const r = (o) => {
|
|
1055
|
+
n(o), o.children?.forEach(r);
|
|
1056
|
+
};
|
|
1057
|
+
t.content && r(t.content);
|
|
1058
|
+
for (const o of t.attachments) o.type === "html" && r(o.node);
|
|
1059
|
+
}
|
|
1060
|
+
function Xt(t, n, r) {
|
|
1061
|
+
const o = structuredClone(t);
|
|
1062
|
+
let i = !1;
|
|
1063
|
+
if (nt(o, (c) => {
|
|
1064
|
+
if (c.id !== n) return;
|
|
1065
|
+
i = !0;
|
|
1066
|
+
const m = /* @__PURE__ */ new Set(), f = (s) => {
|
|
1067
|
+
m.add(s.id), s.children?.forEach(f);
|
|
1068
|
+
};
|
|
1069
|
+
if (c.children?.forEach(f), c.tag) for (const s of o.motion?.steps ?? [])
|
|
1070
|
+
typeof s.target == "string" && (s.target = [...new Set(s.target.split(",").map((g) => m.has(g.match(/"([\w-]+)"/)?.[1] ?? "") ? `[data-pc-node="${c.id}"]` : g))].join(","));
|
|
1071
|
+
c.fit ? (c.fit.spec.blocks = [{ text: r, keep: !0 }], delete c.fit.resolved, c.children = []) : c.tag ? c.children = [{ id: `${c.id}-text`, text: r }] : c.text = r;
|
|
1072
|
+
}), !i) throw new Error(`Unknown content node ${n}`);
|
|
1073
|
+
return o;
|
|
1074
|
+
}
|
|
1075
|
+
function Bt(t) {
|
|
1076
|
+
if (t.version !== 1) throw new Error("Unsupported paper composition version");
|
|
1077
|
+
if (![t.width, t.height, t.environment.pixelRatio].every((o) => Number.isFinite(o) && o > 0)) throw new Error("Invalid composition dimensions");
|
|
1078
|
+
if (![t.rotation, t.environment.grain, t.environment.light.azimuth, t.environment.light.elevation, t.motionSeed].every(Number.isFinite)) throw new Error("Invalid composition environment");
|
|
1079
|
+
if (!Array.isArray(t.assets) || !Array.isArray(t.attachments)) throw new Error("Invalid composition assets or attachments");
|
|
1080
|
+
const n = /* @__PURE__ */ new Set();
|
|
1081
|
+
nt(t, (o) => {
|
|
1082
|
+
if (!/^[\w-]+$/.test(o.id) || n.has(o.id)) throw new Error(`Invalid or duplicate node ID ${o.id}`);
|
|
1083
|
+
n.add(o.id);
|
|
1084
|
+
});
|
|
1085
|
+
const r = /* @__PURE__ */ new Set();
|
|
1086
|
+
for (const o of t.assets) {
|
|
1087
|
+
if (r.has(o.id) || !o.file || o.file.includes("..") || o.file.includes("\\") || /^(?:[a-z]+:|\/)/i.test(o.file)) throw new Error(`Invalid asset ${o.id}`);
|
|
1088
|
+
r.add(o.id);
|
|
1089
|
+
}
|
|
1090
|
+
nt(t, (o) => {
|
|
1091
|
+
if (o.photo && !r.has(o.photo.id)) throw new Error(`Missing photo ${o.photo.id}`);
|
|
1092
|
+
});
|
|
1093
|
+
for (const o of t.attachments) if (o.type === "photo" && !r.has(o.photo.id)) throw new Error(`Missing photo ${o.photo.id}`);
|
|
1094
|
+
for (const o of t.motion?.steps ?? []) {
|
|
1095
|
+
if (typeof o.target != "string" || !/^\[data-pc-node="[\w-]+"\](,\[data-pc-node="[\w-]+"\])*$/.test(o.target)) throw new Error("Motion targets must reference composition node IDs");
|
|
1096
|
+
for (const i of o.target.matchAll(/"([\w-]+)"/g)) if (!n.has(i[1])) throw new Error(`Missing animation target ${i[1]}`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
let qt = 0;
|
|
1100
|
+
function Gt(t, n) {
|
|
1101
|
+
return {
|
|
1102
|
+
w: t.width,
|
|
1103
|
+
h: t.height,
|
|
1104
|
+
rot: t.rotation,
|
|
1105
|
+
paper: t.paper,
|
|
1106
|
+
params: t.params,
|
|
1107
|
+
look: t.sheet,
|
|
1108
|
+
info: t.info,
|
|
1109
|
+
ink: t.content ? J(t.content, n) : null,
|
|
1110
|
+
attach: t.attachments.map((r) => r.type === "html" ? { ...r, el: J(r.node, n) } : structuredClone(r)),
|
|
1111
|
+
motion: t.motion
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
function _t(t, n) {
|
|
1115
|
+
const r = document.createElement("template");
|
|
1116
|
+
r.innerHTML = Ht;
|
|
1117
|
+
const o = J(it(r.content.firstElementChild), n);
|
|
1118
|
+
t.append(o);
|
|
1119
|
+
for (const i of ["ink", "pencil", "stamp", "wash", "stain"]) t.style.setProperty(`--pc-f-${i}`, `url(#${n}-f-${i})`);
|
|
1120
|
+
}
|
|
1121
|
+
function jt(t) {
|
|
1122
|
+
let n = t, r = 0, o = !1, i = !1;
|
|
1123
|
+
const c = document.createElement("div");
|
|
1124
|
+
c.className = "pc-composition";
|
|
1125
|
+
let m = null, f = () => {
|
|
1126
|
+
}, s = Promise.resolve();
|
|
1127
|
+
const g = `pc-${++qt}`;
|
|
1128
|
+
let y;
|
|
1129
|
+
function x(C, d) {
|
|
1130
|
+
const l = C.el;
|
|
1131
|
+
l.style.transform = "none";
|
|
1132
|
+
const w = l.getBoundingClientRect();
|
|
1133
|
+
let k = -24, I = -24, L = l.offsetWidth + 24, P = l.offsetHeight + 24;
|
|
1134
|
+
for (const e of l.querySelectorAll("canvas")) {
|
|
1135
|
+
if (e.classList.contains("lifted")) continue;
|
|
1136
|
+
const a = e.getBoundingClientRect();
|
|
1137
|
+
k = Math.min(k, a.left - w.left), I = Math.min(I, a.top - w.top), L = Math.max(L, a.right - w.left), P = Math.max(P, a.bottom - w.top);
|
|
1138
|
+
}
|
|
1139
|
+
for (const e of l.querySelectorAll(".front > *, .back > *")) {
|
|
1140
|
+
const a = e.getBoundingClientRect();
|
|
1141
|
+
k = Math.min(k, a.left - w.left - 8), I = Math.min(I, a.top - w.top - 8), L = Math.max(L, a.right - w.left + 8), P = Math.max(P, a.bottom - w.top + 8);
|
|
1142
|
+
}
|
|
1143
|
+
const $ = d * Math.PI / 180, F = Math.cos($), b = Math.sin($), R = [[k, I], [L, I], [L, P], [k, P]].map(([e, a]) => [e * F - a * b, e * b + a * F]), T = Math.min(...R.map((e) => e[0])), O = Math.min(...R.map((e) => e[1])), B = Math.max(...R.map((e) => e[0])) - T, D = Math.max(...R.map((e) => e[1])) - O, q = () => {
|
|
1144
|
+
const e = c.parentElement?.clientWidth ?? B, a = n.fit === "contain" ? Math.min(1, e / B) : 1;
|
|
1145
|
+
c.style.width = `${B * a}px`, c.style.height = `${D * a}px`, l.style.left = `${-T * a}px`, l.style.top = `${-O * a}px`, l.style.transform = `scale(${a}) rotate(${d}deg)`;
|
|
1146
|
+
};
|
|
1147
|
+
q(), n.fit === "contain" && c.parentElement && (y = new ResizeObserver(q), y.observe(c.parentElement));
|
|
1148
|
+
}
|
|
1149
|
+
async function K() {
|
|
1150
|
+
if (o) return;
|
|
1151
|
+
const C = ++r;
|
|
1152
|
+
f(), y?.disconnect(), m = null, i = !1, c.replaceChildren(), c.dataset.rendered = "loading";
|
|
1153
|
+
const d = n, l = structuredClone(d.composition);
|
|
1154
|
+
Bt(l);
|
|
1155
|
+
const w = document.createElement("div");
|
|
1156
|
+
w.style.cssText = `width:${l.width}px;height:${l.height}px;background:${l.params.color.base}`, w.textContent = l.info.kind, c.append(w);
|
|
1157
|
+
const k = await Ft(l.assets, d.resolveAsset, l.pen);
|
|
1158
|
+
if (o || C !== r) {
|
|
1159
|
+
k.dispose();
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
const I = d.renderer ?? new vt(), L = Ot((R) => {
|
|
1163
|
+
const T = l.assets.find((O) => O.id === R);
|
|
1164
|
+
if (!T?.photo) throw new Error(`Missing photo metadata: ${R}`);
|
|
1165
|
+
return T.photo;
|
|
1166
|
+
}, (R) => {
|
|
1167
|
+
const T = k.urls.get(R);
|
|
1168
|
+
if (!T) throw new Error(`Missing photo asset: ${R}`);
|
|
1169
|
+
return T;
|
|
1170
|
+
}, () => l.environment.pixelRatio), P = zt({ renderer: I, photos: L, light: l.environment.light, grain: () => l.environment.grain, pixelRatio: () => l.environment.pixelRatio });
|
|
1171
|
+
let $ = null, F = !1;
|
|
1172
|
+
const b = () => {
|
|
1173
|
+
F || (F = !0, $?.dispose(), L.dispose(), P.dispose(), d.renderer || I.dispose(), k.dispose());
|
|
1174
|
+
};
|
|
1175
|
+
f = b;
|
|
1176
|
+
try {
|
|
1177
|
+
const R = Gt(l, g);
|
|
1178
|
+
c.replaceChildren(), _t(c, g);
|
|
1179
|
+
const T = P.buildCard(R);
|
|
1180
|
+
if (c.append(T.el), !I.gl) {
|
|
1181
|
+
T.paper.style.background = l.params.color.base, R.ink && T.paper.append(R.ink), c.dataset.rendered = "fallback", x(T, l.rotation);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (await T.render(l.rotation), o || C !== r) {
|
|
1185
|
+
b();
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (l.motion?.steps.length && R.ink) {
|
|
1189
|
+
const { prepareMotion: O } = await import("./motion.js");
|
|
1190
|
+
if (o || C !== r) {
|
|
1191
|
+
b();
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
$ = O(R, T, mt(l.motionSeed), k.penContext), m = $, d.autoplay !== !1 ? m?.play() : m?.finish();
|
|
1195
|
+
}
|
|
1196
|
+
x(T, l.rotation), i = !0, c.dataset.rendered = "webgl2";
|
|
1197
|
+
} catch (R) {
|
|
1198
|
+
throw b(), R;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function z() {
|
|
1202
|
+
const C = K(), d = r;
|
|
1203
|
+
return C.catch((l) => {
|
|
1204
|
+
throw d === r && !o && (c.dataset.rendered = "error", c.dispatchEvent(new CustomEvent("papererror", { detail: l }))), l;
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
return s = Promise.resolve().then(() => {
|
|
1208
|
+
if (!r && !o) return z();
|
|
1209
|
+
}), s.catch(() => {
|
|
1210
|
+
}), {
|
|
1211
|
+
element: c,
|
|
1212
|
+
get ready() {
|
|
1213
|
+
return s;
|
|
1214
|
+
},
|
|
1215
|
+
get rendered() {
|
|
1216
|
+
return i;
|
|
1217
|
+
},
|
|
1218
|
+
update(C) {
|
|
1219
|
+
return o ? Promise.reject(new Error("Composition disposed")) : (n = { ...n, ...C }, s = z(), s.catch(() => {
|
|
1220
|
+
}), s);
|
|
1221
|
+
},
|
|
1222
|
+
play() {
|
|
1223
|
+
m?.resume();
|
|
1224
|
+
},
|
|
1225
|
+
pause() {
|
|
1226
|
+
m?.pause();
|
|
1227
|
+
},
|
|
1228
|
+
replay() {
|
|
1229
|
+
m?.replay();
|
|
1230
|
+
},
|
|
1231
|
+
seek(C) {
|
|
1232
|
+
m?.seek(C);
|
|
1233
|
+
},
|
|
1234
|
+
dispose() {
|
|
1235
|
+
o || (o = !0, r++, y?.disconnect(), f(), c.remove());
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
export {
|
|
1240
|
+
jt as createPaperComposition,
|
|
1241
|
+
Gt as hydrateComposition,
|
|
1242
|
+
_t as mountCompositionFilters,
|
|
1243
|
+
Xt as setCompositionText,
|
|
1244
|
+
Bt as validateComposition,
|
|
1245
|
+
nt as visitNodes
|
|
1246
|
+
};
|
|
1247
|
+
//# sourceMappingURL=index.js.map
|