paperlab 0.3.1 → 0.4.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/dist/{chunk-XE4E43MD.js → chunk-4ZU5DZEF.js} +1933 -2035
- package/dist/chunk-4ZU5DZEF.js.map +1 -0
- package/dist/index.cjs +490 -745
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +387 -1842
- package/dist/index.d.ts +387 -1842
- package/dist/index.js +183 -431
- package/dist/index.js.map +1 -1
- package/dist/{slots-DUMR64HB.d.cts → slots-BYoLxVZ4.d.cts} +1 -654
- package/dist/{slots-DUMR64HB.d.ts → slots-BYoLxVZ4.d.ts} +1 -654
- package/dist/stage.cjs +0 -14
- package/dist/stage.cjs.map +1 -1
- package/dist/stage.d.cts +2 -178
- package/dist/stage.d.ts +2 -178
- package/dist/stage.js +1 -8
- package/package.json +1 -1
- package/dist/chunk-XE4E43MD.js.map +0 -1
|
@@ -67,6 +67,12 @@ function segmentsForSine(span, amplitude, wavelength, tol = SAG_TOL) {
|
|
|
67
67
|
return segmentsForArc(span, 1 / peakCurvature, tol);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// src/config/schema.ts
|
|
71
|
+
import { z as z14 } from "zod";
|
|
72
|
+
|
|
73
|
+
// src/behaviors/peel.ts
|
|
74
|
+
import { z as z2 } from "zod";
|
|
75
|
+
|
|
70
76
|
// src/deformers/curl.ts
|
|
71
77
|
import { z } from "zod";
|
|
72
78
|
var cornerNames = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
@@ -170,7 +176,6 @@ void FN(inout vec3 p, vec2 uv, float t) {
|
|
|
170
176
|
};
|
|
171
177
|
|
|
172
178
|
// src/behaviors/peel.ts
|
|
173
|
-
import { z as z2 } from "zod";
|
|
174
179
|
var peelOptionsSchema = z2.object({
|
|
175
180
|
progress: z2.number().min(0).max(1).default(0.35),
|
|
176
181
|
/** 'auto' resolves per slot in a `sheet` field (outward-facing corner); standalone it means bottom-right. */
|
|
@@ -544,6 +549,9 @@ var carry = {
|
|
|
544
549
|
}
|
|
545
550
|
};
|
|
546
551
|
|
|
552
|
+
// src/behaviors/flight.ts
|
|
553
|
+
import { z as z10 } from "zod";
|
|
554
|
+
|
|
547
555
|
// src/physics/aero.ts
|
|
548
556
|
function dampTo(state, target, smoothing, dt) {
|
|
549
557
|
const omega = 2 / Math.max(smoothing, 1e-4);
|
|
@@ -584,7 +592,6 @@ function carryDrive(speed) {
|
|
|
584
592
|
}
|
|
585
593
|
|
|
586
594
|
// src/behaviors/flight.ts
|
|
587
|
-
import { z as z10 } from "zod";
|
|
588
595
|
var flightOptionsSchema = z10.object({
|
|
589
596
|
/** Directional wind vector — paper travels ACROSS the scene, not just down. */
|
|
590
597
|
wind: z10.tuple([z10.number().min(-2).max(2), z10.number().min(-2).max(2), z10.number().min(-2).max(2)]).default([0.6, 0.08, 0]),
|
|
@@ -868,7 +875,6 @@ var ribbon = {
|
|
|
868
875
|
};
|
|
869
876
|
|
|
870
877
|
// src/config/schema.ts
|
|
871
|
-
import { z as z14 } from "zod";
|
|
872
878
|
var sheetSchema = z14.object({
|
|
873
879
|
/** World units. A letter sheet is ~1 × 1.4, a receipt ~1 × 2.6. */
|
|
874
880
|
width: z14.number().positive().max(20).default(1),
|
|
@@ -1195,30 +1201,6 @@ function serializePreset(config) {
|
|
|
1195
1201
|
return JSON.stringify(config, null, 2);
|
|
1196
1202
|
}
|
|
1197
1203
|
|
|
1198
|
-
// src/core/sheet.ts
|
|
1199
|
-
import * as THREE from "three";
|
|
1200
|
-
function resolveSegments(sheet2, minSegments = 2, autoSegments = LEGACY_FLAT_SEGMENTS) {
|
|
1201
|
-
const [minX, minY] = typeof minSegments === "number" ? [minSegments, minSegments] : minSegments;
|
|
1202
|
-
if (sheet2.segments !== "auto") {
|
|
1203
|
-
return [Math.max(sheet2.segments, minX, 2), Math.max(sheet2.segments, minY, 2)];
|
|
1204
|
-
}
|
|
1205
|
-
const [wantX, wantY] = typeof autoSegments === "number" ? spreadByAspect(sheet2, quantizeSegments(Math.max(autoSegments, FLAT_SEGMENTS))) : [
|
|
1206
|
-
quantizeSegments(Math.max(wantOrFlat(autoSegments[0]), FLAT_SEGMENTS)),
|
|
1207
|
-
quantizeSegments(Math.max(wantOrFlat(autoSegments[1]), FLAT_SEGMENTS))
|
|
1208
|
-
];
|
|
1209
|
-
return [Math.max(wantX, minX, 2), Math.max(wantY, minY, 2)];
|
|
1210
|
-
}
|
|
1211
|
-
var wantOrFlat = (n) => Number.isFinite(n) ? n : FLAT_SEGMENTS;
|
|
1212
|
-
function spreadByAspect(sheet2, target) {
|
|
1213
|
-
const long = Math.max(sheet2.width, sheet2.height);
|
|
1214
|
-
if (!(long > 0)) return [target, target];
|
|
1215
|
-
return [Math.round(sheet2.width / long * target), Math.round(sheet2.height / long * target)];
|
|
1216
|
-
}
|
|
1217
|
-
function createSheetGeometry(sheet2, minSegments = 2, autoSegments = LEGACY_FLAT_SEGMENTS) {
|
|
1218
|
-
const [sx, sy] = resolveSegments(sheet2, minSegments, autoSegments);
|
|
1219
|
-
return new THREE.PlaneGeometry(sheet2.width, sheet2.height, sx, sy);
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
1204
|
// src/core/stock.ts
|
|
1223
1205
|
var stocks = {
|
|
1224
1206
|
printer: {
|
|
@@ -1581,92 +1563,6 @@ function uniquePresetName(base, taken) {
|
|
|
1581
1563
|
return name;
|
|
1582
1564
|
}
|
|
1583
1565
|
|
|
1584
|
-
// src/content/receipt.ts
|
|
1585
|
-
function receiptTotals(content) {
|
|
1586
|
-
const subtotal = content.items.reduce((sum, item) => sum + item.price, 0);
|
|
1587
|
-
const tax = subtotal * content.taxRate;
|
|
1588
|
-
return { subtotal, tax, total: subtotal + tax };
|
|
1589
|
-
}
|
|
1590
|
-
function barcodeBars(seed) {
|
|
1591
|
-
let h = 2166136261;
|
|
1592
|
-
for (let i = 0; i < seed.length; i++) {
|
|
1593
|
-
h ^= seed.charCodeAt(i);
|
|
1594
|
-
h = Math.imul(h, 16777619);
|
|
1595
|
-
}
|
|
1596
|
-
const bars = [2, 1, 1, 4];
|
|
1597
|
-
for (let i = 0; i < 30; i++) {
|
|
1598
|
-
h = Math.imul(h ^ h >>> 15, 2246822519);
|
|
1599
|
-
bars.push(1 + Math.abs(h) % 4);
|
|
1600
|
-
}
|
|
1601
|
-
bars.push(2, 3, 3, 1, 1, 2);
|
|
1602
|
-
return bars;
|
|
1603
|
-
}
|
|
1604
|
-
var money = (v) => v.toFixed(2);
|
|
1605
|
-
function paintReceipt(ctx, w, h, content, stock) {
|
|
1606
|
-
const ink = stock.inkColor;
|
|
1607
|
-
const pad = w * 0.09;
|
|
1608
|
-
const colWidth = w - pad * 2;
|
|
1609
|
-
const base = Math.round(w / 15);
|
|
1610
|
-
const mono = (size, weight = 400) => `${weight} ${size}px ui-monospace, Menlo, Consolas, monospace`;
|
|
1611
|
-
let y = h * 0.045;
|
|
1612
|
-
const line = (step = 1.6) => y += base * step;
|
|
1613
|
-
const center = (text, size = base, weight = 400) => {
|
|
1614
|
-
ctx.font = mono(size, weight);
|
|
1615
|
-
ctx.textAlign = "center";
|
|
1616
|
-
ctx.fillText(text, w / 2, y);
|
|
1617
|
-
};
|
|
1618
|
-
const row = (left, right, size = base) => {
|
|
1619
|
-
ctx.font = mono(size);
|
|
1620
|
-
ctx.textAlign = "left";
|
|
1621
|
-
ctx.fillText(left, pad, y);
|
|
1622
|
-
ctx.textAlign = "right";
|
|
1623
|
-
ctx.fillText(right, w - pad, y);
|
|
1624
|
-
};
|
|
1625
|
-
const divider = () => {
|
|
1626
|
-
ctx.font = mono(base);
|
|
1627
|
-
ctx.textAlign = "center";
|
|
1628
|
-
ctx.fillText("- ".repeat(Math.floor(colWidth / (base * 1.1))).trim(), w / 2, y);
|
|
1629
|
-
};
|
|
1630
|
-
ctx.fillStyle = ink;
|
|
1631
|
-
ctx.textBaseline = "top";
|
|
1632
|
-
center(content.store.toUpperCase(), base * 1.5, 700);
|
|
1633
|
-
line(2.4);
|
|
1634
|
-
center(content.address.toUpperCase());
|
|
1635
|
-
line(1.8);
|
|
1636
|
-
divider();
|
|
1637
|
-
line(1.8);
|
|
1638
|
-
for (const item of content.items) {
|
|
1639
|
-
row(item.name.toUpperCase(), money(item.price));
|
|
1640
|
-
line();
|
|
1641
|
-
}
|
|
1642
|
-
line(0.4);
|
|
1643
|
-
divider();
|
|
1644
|
-
line(1.8);
|
|
1645
|
-
const totals = receiptTotals(content);
|
|
1646
|
-
row("SUBTOTAL", money(totals.subtotal));
|
|
1647
|
-
line();
|
|
1648
|
-
row(`TAX ${(content.taxRate * 100).toFixed(0)}%`, money(totals.tax));
|
|
1649
|
-
line();
|
|
1650
|
-
row("TOTAL", money(totals.total), base * 1.15);
|
|
1651
|
-
line(2);
|
|
1652
|
-
center(content.timestamp ?? (/* @__PURE__ */ new Date()).toLocaleString("en-GB"), base * 0.9);
|
|
1653
|
-
line(2.2);
|
|
1654
|
-
if (content.barcode) {
|
|
1655
|
-
const bars = barcodeBars(content.store);
|
|
1656
|
-
const modules = bars.reduce((a, b) => a + b, 0);
|
|
1657
|
-
const module = colWidth * 0.85 / modules;
|
|
1658
|
-
const barH = base * 3.2;
|
|
1659
|
-
let x = (w - modules * module) / 2;
|
|
1660
|
-
bars.forEach((width, i) => {
|
|
1661
|
-
if (i % 2 === 0) ctx.fillRect(x, y, width * module, barH);
|
|
1662
|
-
x += width * module;
|
|
1663
|
-
});
|
|
1664
|
-
y += barH;
|
|
1665
|
-
line(1.8);
|
|
1666
|
-
}
|
|
1667
|
-
center(content.footer.toUpperCase(), base * 0.9);
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
1566
|
// src/content/type.ts
|
|
1671
1567
|
function wrapLines(ctx, text, maxWidth, font) {
|
|
1672
1568
|
const previous = ctx.font;
|
|
@@ -1710,6 +1606,9 @@ async function ensureFont(font, size) {
|
|
|
1710
1606
|
}
|
|
1711
1607
|
}
|
|
1712
1608
|
|
|
1609
|
+
// src/deformers/registry.ts
|
|
1610
|
+
import { z as z21 } from "zod";
|
|
1611
|
+
|
|
1713
1612
|
// src/deformers/roll.ts
|
|
1714
1613
|
import { z as z15 } from "zod";
|
|
1715
1614
|
var rollOptionsSchema = z15.object({
|
|
@@ -2233,7 +2132,6 @@ void FN(inout vec3 p, vec2 uv, float t) {
|
|
|
2233
2132
|
};
|
|
2234
2133
|
|
|
2235
2134
|
// src/deformers/registry.ts
|
|
2236
|
-
import { z as z21 } from "zod";
|
|
2237
2135
|
var registry = /* @__PURE__ */ new Map();
|
|
2238
2136
|
function registerDeformer(deformer) {
|
|
2239
2137
|
registry.set(deformer.id, deformer);
|
|
@@ -2277,134 +2175,6 @@ function stackIsAnimated(stack) {
|
|
|
2277
2175
|
return stack.some((i) => i.enabled !== false && registry.get(i.type)?.animated);
|
|
2278
2176
|
}
|
|
2279
2177
|
|
|
2280
|
-
// src/deformers/compose.ts
|
|
2281
|
-
import * as THREE2 from "three";
|
|
2282
|
-
|
|
2283
|
-
// src/core/normals.ts
|
|
2284
|
-
function computeSheetNormals(geometry) {
|
|
2285
|
-
const index = geometry.index;
|
|
2286
|
-
const normalAttr = geometry.attributes.normal;
|
|
2287
|
-
const positionAttr = geometry.attributes.position;
|
|
2288
|
-
if (!index || !normalAttr || !positionAttr) {
|
|
2289
|
-
geometry.computeVertexNormals();
|
|
2290
|
-
return;
|
|
2291
|
-
}
|
|
2292
|
-
const pos = positionAttr.array;
|
|
2293
|
-
const nrm = normalAttr.array;
|
|
2294
|
-
const idx = index.array;
|
|
2295
|
-
nrm.fill(0);
|
|
2296
|
-
for (let i = 0, l = idx.length; i < l; i += 3) {
|
|
2297
|
-
const a = idx[i] * 3;
|
|
2298
|
-
const b = idx[i + 1] * 3;
|
|
2299
|
-
const c = idx[i + 2] * 3;
|
|
2300
|
-
const bx = pos[b];
|
|
2301
|
-
const by = pos[b + 1];
|
|
2302
|
-
const bz = pos[b + 2];
|
|
2303
|
-
const cbx = pos[c] - bx;
|
|
2304
|
-
const cby = pos[c + 1] - by;
|
|
2305
|
-
const cbz = pos[c + 2] - bz;
|
|
2306
|
-
const abx = pos[a] - bx;
|
|
2307
|
-
const aby = pos[a + 1] - by;
|
|
2308
|
-
const abz = pos[a + 2] - bz;
|
|
2309
|
-
const nx = cby * abz - cbz * aby;
|
|
2310
|
-
const ny = cbz * abx - cbx * abz;
|
|
2311
|
-
const nz = cbx * aby - cby * abx;
|
|
2312
|
-
nrm[a] = nrm[a] + nx;
|
|
2313
|
-
nrm[a + 1] = nrm[a + 1] + ny;
|
|
2314
|
-
nrm[a + 2] = nrm[a + 2] + nz;
|
|
2315
|
-
nrm[b] = nrm[b] + nx;
|
|
2316
|
-
nrm[b + 1] = nrm[b + 1] + ny;
|
|
2317
|
-
nrm[b + 2] = nrm[b + 2] + nz;
|
|
2318
|
-
nrm[c] = nrm[c] + nx;
|
|
2319
|
-
nrm[c + 1] = nrm[c + 1] + ny;
|
|
2320
|
-
nrm[c + 2] = nrm[c + 2] + nz;
|
|
2321
|
-
}
|
|
2322
|
-
for (let i = 0, l = nrm.length; i < l; i += 3) {
|
|
2323
|
-
const x = nrm[i];
|
|
2324
|
-
const y = nrm[i + 1];
|
|
2325
|
-
const z26 = nrm[i + 2];
|
|
2326
|
-
const len = Math.sqrt(x * x + y * y + z26 * z26) || 1;
|
|
2327
|
-
nrm[i] = x / len;
|
|
2328
|
-
nrm[i + 1] = y / len;
|
|
2329
|
-
nrm[i + 2] = z26 / len;
|
|
2330
|
-
}
|
|
2331
|
-
normalAttr.needsUpdate = true;
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
// src/deformers/compose.ts
|
|
2335
|
-
var scratchPos = new THREE2.Vector3();
|
|
2336
|
-
var scratchUv = new THREE2.Vector2();
|
|
2337
|
-
var activeFns = [];
|
|
2338
|
-
var activeOptions = [];
|
|
2339
|
-
function applyDeformerStack(geometry, basePositions, stack, ctx) {
|
|
2340
|
-
const position = geometry.attributes.position;
|
|
2341
|
-
const uv = geometry.attributes.uv;
|
|
2342
|
-
const array = position.array;
|
|
2343
|
-
const uvArray = uv.array;
|
|
2344
|
-
const count = position.count;
|
|
2345
|
-
activeFns.length = 0;
|
|
2346
|
-
activeOptions.length = 0;
|
|
2347
|
-
for (const instance of stack) {
|
|
2348
|
-
if (instance.enabled === false) continue;
|
|
2349
|
-
activeFns.push(getDeformer(instance.type).displace);
|
|
2350
|
-
activeOptions.push(instance.options);
|
|
2351
|
-
}
|
|
2352
|
-
array.set(basePositions);
|
|
2353
|
-
for (let k = 0; k < activeFns.length; k++) {
|
|
2354
|
-
const displace = activeFns[k];
|
|
2355
|
-
const options = activeOptions[k];
|
|
2356
|
-
for (let v = 0; v < count; v++) {
|
|
2357
|
-
const i3 = v * 3;
|
|
2358
|
-
const i2 = v * 2;
|
|
2359
|
-
scratchPos.set(array[i3], array[i3 + 1], array[i3 + 2]);
|
|
2360
|
-
scratchUv.set(uvArray[i2], uvArray[i2 + 1]);
|
|
2361
|
-
displace(scratchPos, scratchUv, options, ctx);
|
|
2362
|
-
array[i3] = scratchPos.x;
|
|
2363
|
-
array[i3 + 1] = scratchPos.y;
|
|
2364
|
-
array[i3 + 2] = scratchPos.z;
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
|
-
position.needsUpdate = true;
|
|
2368
|
-
computeSheetNormals(geometry);
|
|
2369
|
-
}
|
|
2370
|
-
function displacePoint(point, uvX, uvY, stack, ctx) {
|
|
2371
|
-
scratchUv.set(uvX, uvY);
|
|
2372
|
-
for (const instance of stack) {
|
|
2373
|
-
if (instance.enabled === false) continue;
|
|
2374
|
-
getDeformer(instance.type).displace(point, scratchUv, instance.options, ctx);
|
|
2375
|
-
}
|
|
2376
|
-
return point;
|
|
2377
|
-
}
|
|
2378
|
-
function stackMinSegments(stack, sheet2) {
|
|
2379
|
-
const out = [2, 2];
|
|
2380
|
-
for (const instance of stack) {
|
|
2381
|
-
const deformer = getDeformer(instance.type);
|
|
2382
|
-
const floor = deformer.geometry?.minSegments;
|
|
2383
|
-
if (!floor) continue;
|
|
2384
|
-
take(out, deformer, instance.options, sheet2, floor);
|
|
2385
|
-
}
|
|
2386
|
-
return out;
|
|
2387
|
-
}
|
|
2388
|
-
function stackAutoSegments(stack, sheet2) {
|
|
2389
|
-
const out = [0, 0];
|
|
2390
|
-
for (const instance of stack) {
|
|
2391
|
-
if (instance.enabled === false) continue;
|
|
2392
|
-
const deformer = getDeformer(instance.type);
|
|
2393
|
-
const geometry = deformer.geometry;
|
|
2394
|
-
if (!geometry) continue;
|
|
2395
|
-
const want = geometry.autoSegments ? geometry.autoSegments(instance.options, sheet2) : geometry.minSegments ?? 0;
|
|
2396
|
-
take(out, deformer, instance.options, sheet2, want);
|
|
2397
|
-
}
|
|
2398
|
-
return out;
|
|
2399
|
-
}
|
|
2400
|
-
function take(out, deformer, options, sheet2, demand) {
|
|
2401
|
-
const declared = deformer.geometry?.axis?.(options, sheet2);
|
|
2402
|
-
const angle = typeof declared === "number" && Number.isFinite(declared) ? declared : null;
|
|
2403
|
-
const [x, y] = axialSegments(sheet2, angle, demand);
|
|
2404
|
-
if (x > out[0]) out[0] = x;
|
|
2405
|
-
if (y > out[1]) out[1] = y;
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
2178
|
// src/behaviors/registry.ts
|
|
2409
2179
|
var registry2 = /* @__PURE__ */ new Map();
|
|
2410
2180
|
function registerBehavior(behavior) {
|
|
@@ -2495,209 +2265,6 @@ function getIdlePreset(name) {
|
|
|
2495
2265
|
return idlePresets[name];
|
|
2496
2266
|
}
|
|
2497
2267
|
|
|
2498
|
-
// src/physics/cloth.ts
|
|
2499
|
-
var FIXED_DT = 1 / 120;
|
|
2500
|
-
var SOLVER_ITERATIONS = 5;
|
|
2501
|
-
var SLEEP_EPSILON = 1e-6;
|
|
2502
|
-
var SLEEP_FRAMES = 45;
|
|
2503
|
-
var ClothSim = class {
|
|
2504
|
-
cols;
|
|
2505
|
-
rows;
|
|
2506
|
-
count;
|
|
2507
|
-
positions;
|
|
2508
|
-
prev;
|
|
2509
|
-
pinned;
|
|
2510
|
-
pinTargets;
|
|
2511
|
-
constraints = [];
|
|
2512
|
-
params;
|
|
2513
|
-
time = 0;
|
|
2514
|
-
accumulator = 0;
|
|
2515
|
-
stillFrames = 0;
|
|
2516
|
-
grabbedIndex = -1;
|
|
2517
|
-
/** True when the sim has settled and steps are skipped. */
|
|
2518
|
-
asleep = false;
|
|
2519
|
-
constructor(cols, rows, width, height, pins, params) {
|
|
2520
|
-
this.cols = cols;
|
|
2521
|
-
this.rows = rows;
|
|
2522
|
-
this.count = cols * rows;
|
|
2523
|
-
this.params = { ...params };
|
|
2524
|
-
this.positions = new Float32Array(this.count * 3);
|
|
2525
|
-
this.prev = new Float32Array(this.count * 3);
|
|
2526
|
-
this.pinned = new Uint8Array(this.count);
|
|
2527
|
-
this.pinTargets = new Float32Array(this.count * 3);
|
|
2528
|
-
for (let r = 0; r < rows; r++) {
|
|
2529
|
-
for (let c = 0; c < cols; c++) {
|
|
2530
|
-
const i3 = (r * cols + c) * 3;
|
|
2531
|
-
this.positions[i3] = (c / (cols - 1) - 0.5) * width;
|
|
2532
|
-
this.positions[i3 + 1] = (0.5 - r / (rows - 1)) * height;
|
|
2533
|
-
this.positions[i3 + 2] = 0;
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
this.prev.set(this.positions);
|
|
2537
|
-
const idx = (r, c) => r * cols + c;
|
|
2538
|
-
const link = (a, b, kind) => {
|
|
2539
|
-
const dx = this.positions[a * 3] - this.positions[b * 3];
|
|
2540
|
-
const dy = this.positions[a * 3 + 1] - this.positions[b * 3 + 1];
|
|
2541
|
-
this.constraints.push({ a, b, rest: Math.hypot(dx, dy), kind });
|
|
2542
|
-
};
|
|
2543
|
-
for (let r = 0; r < rows; r++) {
|
|
2544
|
-
for (let c = 0; c < cols; c++) {
|
|
2545
|
-
if (c + 1 < cols) link(idx(r, c), idx(r, c + 1), 0);
|
|
2546
|
-
if (r + 1 < rows) link(idx(r, c), idx(r + 1, c), 0);
|
|
2547
|
-
if (c + 1 < cols && r + 1 < rows) {
|
|
2548
|
-
link(idx(r, c), idx(r + 1, c + 1), 1);
|
|
2549
|
-
link(idx(r, c + 1), idx(r + 1, c), 1);
|
|
2550
|
-
}
|
|
2551
|
-
if (c + 2 < cols) link(idx(r, c), idx(r, c + 2), 2);
|
|
2552
|
-
if (r + 2 < rows) link(idx(r, c), idx(r + 2, c), 2);
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
const pin = (r, c) => {
|
|
2556
|
-
const i = idx(r, c);
|
|
2557
|
-
this.pinned[i] = 1;
|
|
2558
|
-
this.pinTargets.set(this.positions.subarray(i * 3, i * 3 + 3), i * 3);
|
|
2559
|
-
};
|
|
2560
|
-
if (pins === "top-edge") for (let c = 0; c < cols; c++) pin(0, c);
|
|
2561
|
-
if (pins === "top-corners") {
|
|
2562
|
-
pin(0, 0);
|
|
2563
|
-
pin(0, cols - 1);
|
|
2564
|
-
}
|
|
2565
|
-
if (pins === "corner") pin(0, 0);
|
|
2566
|
-
}
|
|
2567
|
-
setParams(params) {
|
|
2568
|
-
let changed = false;
|
|
2569
|
-
for (const key of ["stiffness", "gravity", "wind", "floor"]) {
|
|
2570
|
-
const value = params[key];
|
|
2571
|
-
if (value !== void 0 && value !== this.params[key]) {
|
|
2572
|
-
this.params[key] = value;
|
|
2573
|
-
changed = true;
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
if (changed) this.wake();
|
|
2577
|
-
}
|
|
2578
|
-
wake() {
|
|
2579
|
-
this.asleep = false;
|
|
2580
|
-
this.stillFrames = 0;
|
|
2581
|
-
}
|
|
2582
|
-
/** Nearest particle to a local-space point — the grab interface. */
|
|
2583
|
-
grabNearest(x, y, z26) {
|
|
2584
|
-
let best = -1;
|
|
2585
|
-
let bestDist = Infinity;
|
|
2586
|
-
for (let i = 0; i < this.count; i++) {
|
|
2587
|
-
const dx = this.positions[i * 3] - x;
|
|
2588
|
-
const dy = this.positions[i * 3 + 1] - y;
|
|
2589
|
-
const dz = this.positions[i * 3 + 2] - z26;
|
|
2590
|
-
const d = dx * dx + dy * dy + dz * dz;
|
|
2591
|
-
if (d < bestDist) {
|
|
2592
|
-
bestDist = d;
|
|
2593
|
-
best = i;
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
this.grabbedIndex = best;
|
|
2597
|
-
this.wake();
|
|
2598
|
-
return best;
|
|
2599
|
-
}
|
|
2600
|
-
moveGrab(x, y, z26) {
|
|
2601
|
-
if (this.grabbedIndex < 0) return;
|
|
2602
|
-
const i3 = this.grabbedIndex * 3;
|
|
2603
|
-
this.positions[i3] = x;
|
|
2604
|
-
this.positions[i3 + 1] = y;
|
|
2605
|
-
this.positions[i3 + 2] = z26;
|
|
2606
|
-
this.prev[i3] = x;
|
|
2607
|
-
this.prev[i3 + 1] = y;
|
|
2608
|
-
this.prev[i3 + 2] = z26;
|
|
2609
|
-
this.wake();
|
|
2610
|
-
}
|
|
2611
|
-
release() {
|
|
2612
|
-
this.grabbedIndex = -1;
|
|
2613
|
-
}
|
|
2614
|
-
step(delta) {
|
|
2615
|
-
if (this.asleep) return;
|
|
2616
|
-
this.accumulator = Math.min(this.accumulator + delta, FIXED_DT * 4);
|
|
2617
|
-
while (this.accumulator >= FIXED_DT) {
|
|
2618
|
-
this.substep(FIXED_DT);
|
|
2619
|
-
this.accumulator -= FIXED_DT;
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
substep(dt) {
|
|
2623
|
-
const { gravity, wind, stiffness, floor } = this.params;
|
|
2624
|
-
const p = this.positions;
|
|
2625
|
-
const damping = 0.985;
|
|
2626
|
-
const dt2 = dt * dt;
|
|
2627
|
-
this.time += dt;
|
|
2628
|
-
let maxTravel = 0;
|
|
2629
|
-
for (let i = 0; i < this.count; i++) {
|
|
2630
|
-
const i3 = i * 3;
|
|
2631
|
-
if (this.pinned[i] || i === this.grabbedIndex) {
|
|
2632
|
-
if (this.pinned[i]) {
|
|
2633
|
-
p[i3] = this.pinTargets[i3];
|
|
2634
|
-
p[i3 + 1] = this.pinTargets[i3 + 1];
|
|
2635
|
-
p[i3 + 2] = this.pinTargets[i3 + 2];
|
|
2636
|
-
}
|
|
2637
|
-
this.prev[i3] = p[i3];
|
|
2638
|
-
this.prev[i3 + 1] = p[i3 + 1];
|
|
2639
|
-
this.prev[i3 + 2] = p[i3 + 2];
|
|
2640
|
-
continue;
|
|
2641
|
-
}
|
|
2642
|
-
const x = p[i3];
|
|
2643
|
-
const y = p[i3 + 1];
|
|
2644
|
-
const z26 = p[i3 + 2];
|
|
2645
|
-
const gust2 = wind * (0.55 + 0.45 * Math.sin(this.time * 1.7 + x * 2.1 + y * 1.3)) * 0.9;
|
|
2646
|
-
const ax = gust2 * 0.25;
|
|
2647
|
-
const az = gust2;
|
|
2648
|
-
const vx = (x - this.prev[i3]) * damping;
|
|
2649
|
-
const vy = (y - this.prev[i3 + 1]) * damping;
|
|
2650
|
-
const vz = (z26 - this.prev[i3 + 2]) * damping;
|
|
2651
|
-
this.prev[i3] = x;
|
|
2652
|
-
this.prev[i3 + 1] = y;
|
|
2653
|
-
this.prev[i3 + 2] = z26;
|
|
2654
|
-
p[i3] = x + vx + ax * dt2;
|
|
2655
|
-
p[i3 + 1] = y + vy - gravity * 3.2 * dt2;
|
|
2656
|
-
p[i3 + 2] = z26 + vz + az * dt2;
|
|
2657
|
-
maxTravel = Math.max(maxTravel, vx * vx + vy * vy + vz * vz);
|
|
2658
|
-
}
|
|
2659
|
-
for (let iter = 0; iter < SOLVER_ITERATIONS; iter++) {
|
|
2660
|
-
for (const c of this.constraints) {
|
|
2661
|
-
const k = c.kind === 2 ? 0.25 + stiffness * 0.7 : c.kind === 1 ? 0.85 : 1;
|
|
2662
|
-
const a3 = c.a * 3;
|
|
2663
|
-
const b3 = c.b * 3;
|
|
2664
|
-
const dx = p[b3] - p[a3];
|
|
2665
|
-
const dy = p[b3 + 1] - p[a3 + 1];
|
|
2666
|
-
const dz = p[b3 + 2] - p[a3 + 2];
|
|
2667
|
-
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
2668
|
-
if (dist === 0) continue;
|
|
2669
|
-
const diff = (dist - c.rest) / dist * 0.5 * k;
|
|
2670
|
-
const aPinned = this.pinned[c.a] || c.a === this.grabbedIndex;
|
|
2671
|
-
const bPinned = this.pinned[c.b] || c.b === this.grabbedIndex;
|
|
2672
|
-
if (aPinned && bPinned) continue;
|
|
2673
|
-
const aw = aPinned ? 0 : bPinned ? 2 : 1;
|
|
2674
|
-
const bw = bPinned ? 0 : aPinned ? 2 : 1;
|
|
2675
|
-
p[a3] = p[a3] + dx * diff * aw;
|
|
2676
|
-
p[a3 + 1] = p[a3 + 1] + dy * diff * aw;
|
|
2677
|
-
p[a3 + 2] = p[a3 + 2] + dz * diff * aw;
|
|
2678
|
-
p[b3] = p[b3] - dx * diff * bw;
|
|
2679
|
-
p[b3 + 1] = p[b3 + 1] - dy * diff * bw;
|
|
2680
|
-
p[b3 + 2] = p[b3 + 2] - dz * diff * bw;
|
|
2681
|
-
}
|
|
2682
|
-
}
|
|
2683
|
-
for (let i = 0; i < this.count; i++) {
|
|
2684
|
-
const i3 = i * 3;
|
|
2685
|
-
if (p[i3 + 1] < floor) {
|
|
2686
|
-
p[i3 + 1] = floor;
|
|
2687
|
-
this.prev[i3] = this.prev[i3] + (p[i3] - this.prev[i3]) * 0.5;
|
|
2688
|
-
this.prev[i3 + 2] = this.prev[i3 + 2] + (p[i3 + 2] - this.prev[i3 + 2]) * 0.5;
|
|
2689
|
-
}
|
|
2690
|
-
}
|
|
2691
|
-
if (wind === 0 && this.grabbedIndex < 0) {
|
|
2692
|
-
if (maxTravel < SLEEP_EPSILON) {
|
|
2693
|
-
if (++this.stillFrames > SLEEP_FRAMES) this.asleep = true;
|
|
2694
|
-
} else {
|
|
2695
|
-
this.stillFrames = 0;
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
};
|
|
2700
|
-
|
|
2701
2268
|
// src/scene/lighting.ts
|
|
2702
2269
|
import { z as z22 } from "zod";
|
|
2703
2270
|
var lightingPresets = {
|
|
@@ -2943,1060 +2510,1505 @@ function resolveFog(fog, haze) {
|
|
|
2943
2510
|
return { color: fog.color, near: fog.near / haze, far: fog.far / haze };
|
|
2944
2511
|
}
|
|
2945
2512
|
|
|
2946
|
-
// src/
|
|
2947
|
-
import
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
varying vec3 vPlViewDir;
|
|
2953
|
-
`
|
|
2954
|
-
);
|
|
2955
|
-
function translucencyVertexChunk(slots) {
|
|
2956
|
-
return (
|
|
2957
|
-
/* glsl */
|
|
2958
|
-
`
|
|
2959
|
-
{
|
|
2960
|
-
mat4 plModel = ${slots.model};
|
|
2961
|
-
vec4 plWorld = plModel * vec4(${slots.position}, 1.0);
|
|
2962
|
-
// Uniform scale only \u2014 layouts scale sheets evenly, so the plain 3\xD73 is
|
|
2963
|
-
// the correct normal matrix here and skips an inverse-transpose.
|
|
2964
|
-
vPlWorldNormal = normalize(mat3(plModel) * ${slots.normal});
|
|
2965
|
-
vPlViewDir = cameraPosition - plWorld.xyz;
|
|
2966
|
-
}
|
|
2967
|
-
`
|
|
2968
|
-
);
|
|
2513
|
+
// src/scene/rig.tsx
|
|
2514
|
+
import { createContext, useContext } from "react";
|
|
2515
|
+
import { jsx } from "react/jsx-runtime";
|
|
2516
|
+
var LightRigContext = createContext(null);
|
|
2517
|
+
function LightRig({ rig, children }) {
|
|
2518
|
+
return /* @__PURE__ */ jsx(LightRigContext.Provider, { value: rig, children });
|
|
2969
2519
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
/* glsl */
|
|
2973
|
-
`
|
|
2974
|
-
uniform float uTranslucency;
|
|
2975
|
-
uniform vec3 uBackLightDir;
|
|
2976
|
-
uniform vec3 uBackLightColor;
|
|
2977
|
-
uniform float uAmbientTransmission;
|
|
2978
|
-
${TRANSLUCENCY_VARYINGS}
|
|
2979
|
-
|
|
2980
|
-
vec3 plTransmission(vec3 inkFilter) {
|
|
2981
|
-
if (uTranslucency <= 0.0) return vec3(0.0);
|
|
2982
|
-
vec3 n = normalize(vPlWorldNormal);
|
|
2983
|
-
// Sheets render double-sided; the back face needs the normal it actually shows.
|
|
2984
|
-
if (!gl_FrontFacing) n = -n;
|
|
2985
|
-
// The lamp is BEHIND this sheet when the face we are looking at points away
|
|
2986
|
-
// from it \u2014 that is the whole test.
|
|
2987
|
-
float behind = clamp(-dot(n, uBackLightDir), 0.0, 1.0);
|
|
2988
|
-
// A grazing view looks through more paper, and more paper passes less light.
|
|
2989
|
-
float thickness = abs(dot(n, normalize(vPlViewDir)));
|
|
2990
|
-
// Paper in a lit room glows whatever way it is turned \u2014 a sheet standing
|
|
2991
|
-
// edge-on to the only lamp is not black. Without this floor, a banner
|
|
2992
|
-
// whose face runs parallel to the key light gets neither diffuse nor
|
|
2993
|
-
// transmission and drops out of the picture entirely.
|
|
2994
|
-
vec3 arriving = uBackLightColor * behind + uAmbientTransmission;
|
|
2995
|
-
return arriving * uTranslucency * mix(0.25, 1.0, thickness) * inkFilter;
|
|
2520
|
+
function useLightRig(own) {
|
|
2521
|
+
return useContext(LightRigContext) ?? getLightingPreset(own);
|
|
2996
2522
|
}
|
|
2997
|
-
`
|
|
2998
|
-
);
|
|
2999
|
-
function translucencyValues(translucency, lighting) {
|
|
3000
|
-
const preset = typeof lighting === "string" ? getLightingPreset(lighting) : lighting;
|
|
3001
|
-
const [x, y, z26] = preset.key.position;
|
|
3002
|
-
const direction = new THREE3.Vector3(x, y, z26);
|
|
3003
|
-
if (direction.lengthSq() < 1e-12) direction.set(0, 1, 0);
|
|
3004
|
-
direction.normalize();
|
|
3005
|
-
const color = new THREE3.Color(preset.key.color).multiplyScalar(preset.key.intensity * TRANSMISSION_GAIN);
|
|
3006
|
-
return { translucency, direction, color, ambient: preset.ambient * TRANSMISSION_GAIN };
|
|
3007
|
-
}
|
|
3008
|
-
function translucencyUniforms(translucency, lighting) {
|
|
3009
|
-
const values = translucencyValues(translucency, lighting);
|
|
3010
|
-
return {
|
|
3011
|
-
uTranslucency: { value: values.translucency },
|
|
3012
|
-
uBackLightDir: { value: values.direction },
|
|
3013
|
-
uBackLightColor: { value: values.color },
|
|
3014
|
-
uAmbientTransmission: { value: values.ambient }
|
|
3015
|
-
};
|
|
3016
|
-
}
|
|
3017
|
-
|
|
3018
|
-
// src/surface/compose.ts
|
|
3019
|
-
import * as THREE4 from "three";
|
|
3020
|
-
var VERTEX = (
|
|
3021
|
-
/* glsl */
|
|
3022
|
-
`
|
|
3023
|
-
varying vec2 vPaperUv;
|
|
3024
|
-
${TRANSLUCENCY_VARYINGS}
|
|
3025
|
-
void main() {
|
|
3026
|
-
vPaperUv = uv;
|
|
3027
|
-
${translucencyVertexChunk({ model: "modelMatrix", position: "position", normal: "normal" })}
|
|
3028
|
-
}
|
|
3029
|
-
`
|
|
3030
|
-
);
|
|
3031
|
-
var HELPERS = (
|
|
3032
|
-
/* glsl */
|
|
3033
|
-
`
|
|
3034
|
-
varying vec2 vPaperUv;
|
|
3035
|
-
uniform float uBackDarken;
|
|
3036
2523
|
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
}
|
|
2524
|
+
// src/a11y/index.tsx
|
|
2525
|
+
import { useEffect, useState } from "react";
|
|
3040
2526
|
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
return
|
|
3046
|
-
mix(plHash(i), plHash(i + vec2(1.0, 0.0)), u.x),
|
|
3047
|
-
mix(plHash(i + vec2(0.0, 1.0)), plHash(i + vec2(1.0, 1.0)), u.x),
|
|
3048
|
-
u.y
|
|
3049
|
-
);
|
|
2527
|
+
// src/content/receipt.ts
|
|
2528
|
+
function receiptTotals(content) {
|
|
2529
|
+
const subtotal = content.items.reduce((sum, item) => sum + item.price, 0);
|
|
2530
|
+
const tax = subtotal * content.taxRate;
|
|
2531
|
+
return { subtotal, tax, total: subtotal + tax };
|
|
3050
2532
|
}
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
v += a * plNoise(p);
|
|
3057
|
-
p *= 2.03;
|
|
3058
|
-
a *= 0.5;
|
|
2533
|
+
function barcodeBars(seed) {
|
|
2534
|
+
let h = 2166136261;
|
|
2535
|
+
for (let i = 0; i < seed.length; i++) {
|
|
2536
|
+
h ^= seed.charCodeAt(i);
|
|
2537
|
+
h = Math.imul(h, 16777619);
|
|
3059
2538
|
}
|
|
3060
|
-
|
|
2539
|
+
const bars = [2, 1, 1, 4];
|
|
2540
|
+
for (let i = 0; i < 30; i++) {
|
|
2541
|
+
h = Math.imul(h ^ h >>> 15, 2246822519);
|
|
2542
|
+
bars.push(1 + Math.abs(h) % 4);
|
|
2543
|
+
}
|
|
2544
|
+
bars.push(2, 3, 3, 1, 1, 2);
|
|
2545
|
+
return bars;
|
|
3061
2546
|
}
|
|
3062
|
-
|
|
3063
|
-
)
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
2547
|
+
var money = (v) => v.toFixed(2);
|
|
2548
|
+
function paintReceipt(ctx, w, h, content, stock) {
|
|
2549
|
+
const ink = stock.inkColor;
|
|
2550
|
+
const pad = w * 0.09;
|
|
2551
|
+
const colWidth = w - pad * 2;
|
|
2552
|
+
const base = Math.round(w / 15);
|
|
2553
|
+
const mono = (size, weight = 400) => `${weight} ${size}px ui-monospace, Menlo, Consolas, monospace`;
|
|
2554
|
+
let y = h * 0.045;
|
|
2555
|
+
const line = (step = 1.6) => y += base * step;
|
|
2556
|
+
const center = (text, size = base, weight = 400) => {
|
|
2557
|
+
ctx.font = mono(size, weight);
|
|
2558
|
+
ctx.textAlign = "center";
|
|
2559
|
+
ctx.fillText(text, w / 2, y);
|
|
2560
|
+
};
|
|
2561
|
+
const row = (left, right, size = base) => {
|
|
2562
|
+
ctx.font = mono(size);
|
|
2563
|
+
ctx.textAlign = "left";
|
|
2564
|
+
ctx.fillText(left, pad, y);
|
|
2565
|
+
ctx.textAlign = "right";
|
|
2566
|
+
ctx.fillText(right, w - pad, y);
|
|
2567
|
+
};
|
|
2568
|
+
const divider = () => {
|
|
2569
|
+
ctx.font = mono(base);
|
|
2570
|
+
ctx.textAlign = "center";
|
|
2571
|
+
ctx.fillText("- ".repeat(Math.floor(colWidth / (base * 1.1))).trim(), w / 2, y);
|
|
2572
|
+
};
|
|
2573
|
+
ctx.fillStyle = ink;
|
|
2574
|
+
ctx.textBaseline = "top";
|
|
2575
|
+
center(content.store.toUpperCase(), base * 1.5, 700);
|
|
2576
|
+
line(2.4);
|
|
2577
|
+
center(content.address.toUpperCase());
|
|
2578
|
+
line(1.8);
|
|
2579
|
+
divider();
|
|
2580
|
+
line(1.8);
|
|
2581
|
+
for (const item of content.items) {
|
|
2582
|
+
row(item.name.toUpperCase(), money(item.price));
|
|
2583
|
+
line();
|
|
2584
|
+
}
|
|
2585
|
+
line(0.4);
|
|
2586
|
+
divider();
|
|
2587
|
+
line(1.8);
|
|
2588
|
+
const totals = receiptTotals(content);
|
|
2589
|
+
row("SUBTOTAL", money(totals.subtotal));
|
|
2590
|
+
line();
|
|
2591
|
+
row(`TAX ${(content.taxRate * 100).toFixed(0)}%`, money(totals.tax));
|
|
2592
|
+
line();
|
|
2593
|
+
row("TOTAL", money(totals.total), base * 1.15);
|
|
2594
|
+
line(2);
|
|
2595
|
+
center(content.timestamp ?? (/* @__PURE__ */ new Date()).toLocaleString("en-GB"), base * 0.9);
|
|
2596
|
+
line(2.2);
|
|
2597
|
+
if (content.barcode) {
|
|
2598
|
+
const bars = barcodeBars(content.store);
|
|
2599
|
+
const modules = bars.reduce((a, b) => a + b, 0);
|
|
2600
|
+
const module = colWidth * 0.85 / modules;
|
|
2601
|
+
const barH = base * 3.2;
|
|
2602
|
+
let x = (w - modules * module) / 2;
|
|
2603
|
+
bars.forEach((width, i) => {
|
|
2604
|
+
if (i % 2 === 0) ctx.fillRect(x, y, width * module, barH);
|
|
2605
|
+
x += width * module;
|
|
2606
|
+
});
|
|
2607
|
+
y += barH;
|
|
2608
|
+
line(1.8);
|
|
3086
2609
|
}
|
|
2610
|
+
center(content.footer.toUpperCase(), base * 0.9);
|
|
3087
2611
|
}
|
|
3088
|
-
`
|
|
3089
|
-
);
|
|
3090
|
-
var DECKLE_CHUNK = (
|
|
3091
|
-
/* glsl */
|
|
3092
|
-
`
|
|
3093
|
-
uniform vec4 uDeckleEdges; // top, right, bottom, left
|
|
3094
|
-
uniform float uDeckleRoughness;
|
|
3095
2612
|
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
if (
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
2613
|
+
// src/a11y/index.tsx
|
|
2614
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
2615
|
+
function usePrefersReducedMotion(override) {
|
|
2616
|
+
const [system, setSystem] = useState(
|
|
2617
|
+
() => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
2618
|
+
);
|
|
2619
|
+
useEffect(() => {
|
|
2620
|
+
const query = window.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
2621
|
+
if (!query) return;
|
|
2622
|
+
const onChange = () => setSystem(query.matches);
|
|
2623
|
+
query.addEventListener("change", onChange);
|
|
2624
|
+
return () => query.removeEventListener("change", onChange);
|
|
2625
|
+
}, []);
|
|
2626
|
+
return override ?? system;
|
|
2627
|
+
}
|
|
2628
|
+
var webglSupport = null;
|
|
2629
|
+
function supportsWebGL() {
|
|
2630
|
+
if (webglSupport !== null) return webglSupport;
|
|
2631
|
+
try {
|
|
2632
|
+
const canvas = document.createElement("canvas");
|
|
2633
|
+
webglSupport = Boolean(canvas.getContext("webgl2") ?? canvas.getContext("webgl"));
|
|
2634
|
+
} catch {
|
|
2635
|
+
webglSupport = false;
|
|
3111
2636
|
}
|
|
3112
|
-
|
|
3113
|
-
color.rgb = mix(color.rgb, vec3(1.0), fiberBand * 0.35);
|
|
2637
|
+
return webglSupport;
|
|
3114
2638
|
}
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
uniform int uCreaseCount;
|
|
3124
|
-
|
|
3125
|
-
void plCrease(inout vec4 color, inout float rough) {
|
|
3126
|
-
vec2 dir = vec2(cos(uCreaseAngle), sin(uCreaseAngle));
|
|
3127
|
-
// Coordinate across the crease lines (0..1 over the sheet).
|
|
3128
|
-
float t = dot(vPaperUv - 0.5, vec2(-dir.y, dir.x)) + 0.5;
|
|
3129
|
-
for (int i = 0; i < 4; i++) {
|
|
3130
|
-
if (i >= uCreaseCount) break;
|
|
3131
|
-
float d = abs(t - uCreasePositions[i]);
|
|
3132
|
-
float shadow = smoothstep(0.014, 0.0, d);
|
|
3133
|
-
float sheen = smoothstep(0.02, 0.006, d) - smoothstep(0.006, 0.0, d);
|
|
3134
|
-
color.rgb *= 1.0 - shadow * uCreaseStrength * 0.28;
|
|
3135
|
-
color.rgb += sheen * uCreaseStrength * 0.05;
|
|
3136
|
-
rough = clamp(rough + shadow * uCreaseStrength * 0.2, 0.0, 1.0);
|
|
2639
|
+
function contentText(config) {
|
|
2640
|
+
const content = config.content;
|
|
2641
|
+
if (content.type === "text") return content.text;
|
|
2642
|
+
if (content.type === "image") return content.alt ?? "An image printed on paper.";
|
|
2643
|
+
if (content.type === "receipt") {
|
|
2644
|
+
const totals = receiptTotals(content);
|
|
2645
|
+
const items = content.items.map((i) => `${i.name} ${i.price.toFixed(2)}`).join(", ");
|
|
2646
|
+
return `Receipt from ${content.store}: ${items}. Total ${totals.total.toFixed(2)}. ${content.footer}`;
|
|
3137
2647
|
}
|
|
2648
|
+
return "A blank sheet of paper.";
|
|
3138
2649
|
}
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
2650
|
+
var visuallyHidden = {
|
|
2651
|
+
position: "absolute",
|
|
2652
|
+
width: 1,
|
|
2653
|
+
height: 1,
|
|
2654
|
+
padding: 0,
|
|
2655
|
+
margin: -1,
|
|
2656
|
+
overflow: "hidden",
|
|
2657
|
+
clip: "rect(0 0 0 0)",
|
|
2658
|
+
whiteSpace: "nowrap",
|
|
2659
|
+
border: 0
|
|
2660
|
+
};
|
|
2661
|
+
function PaperMirror({ config }) {
|
|
2662
|
+
return /* @__PURE__ */ jsx2("div", { style: visuallyHidden, "aria-hidden": false, children: contentText(config) });
|
|
2663
|
+
}
|
|
2664
|
+
function PaperFallback({ config }) {
|
|
2665
|
+
const stock = getStock(config.stock);
|
|
2666
|
+
const isImage = config.content.type === "image";
|
|
2667
|
+
return /* @__PURE__ */ jsx2(
|
|
2668
|
+
"div",
|
|
2669
|
+
{
|
|
2670
|
+
role: "img",
|
|
2671
|
+
"aria-label": contentText(config),
|
|
2672
|
+
style: {
|
|
2673
|
+
width: "100%",
|
|
2674
|
+
height: "100%",
|
|
2675
|
+
display: "grid",
|
|
2676
|
+
placeItems: "center",
|
|
2677
|
+
background: "transparent"
|
|
2678
|
+
},
|
|
2679
|
+
children: /* @__PURE__ */ jsx2(
|
|
2680
|
+
"div",
|
|
2681
|
+
{
|
|
2682
|
+
style: {
|
|
2683
|
+
aspectRatio: `${config.sheet.width} / ${config.sheet.height}`,
|
|
2684
|
+
maxWidth: "80%",
|
|
2685
|
+
maxHeight: "90%",
|
|
2686
|
+
background: stock.color,
|
|
2687
|
+
color: stock.inkColor,
|
|
2688
|
+
boxShadow: "0 6px 24px rgba(0,0,0,0.25)",
|
|
2689
|
+
padding: "8%",
|
|
2690
|
+
overflow: "hidden",
|
|
2691
|
+
fontFamily: config.content.type === "receipt" ? "ui-monospace, monospace" : "Georgia, serif",
|
|
2692
|
+
whiteSpace: "pre-wrap",
|
|
2693
|
+
fontSize: 14
|
|
2694
|
+
},
|
|
2695
|
+
children: isImage && config.content.type === "image" ? /* @__PURE__ */ jsx2(
|
|
2696
|
+
"img",
|
|
2697
|
+
{
|
|
2698
|
+
src: config.content.src,
|
|
2699
|
+
alt: config.content.alt ?? "",
|
|
2700
|
+
style: { width: "100%", height: "100%", objectFit: "cover", margin: "-8%" }
|
|
2701
|
+
}
|
|
2702
|
+
) : contentText(config)
|
|
2703
|
+
}
|
|
2704
|
+
)
|
|
3176
2705
|
}
|
|
3177
|
-
|
|
3178
|
-
color.rgb = mix(color.rgb, vec3(1.0), fiber * 0.4);
|
|
2706
|
+
);
|
|
3179
2707
|
}
|
|
3180
|
-
`
|
|
3181
|
-
);
|
|
3182
|
-
var AGING_CHUNK = (
|
|
3183
|
-
/* glsl */
|
|
3184
|
-
`
|
|
3185
|
-
uniform float uAgingAmount;
|
|
3186
2708
|
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
2709
|
+
// src/states/machine.ts
|
|
2710
|
+
import { gsap } from "gsap";
|
|
2711
|
+
var stateEventTransitions = {
|
|
2712
|
+
rest: { enter: "hover" },
|
|
2713
|
+
hover: { leave: "rest", down: "pressed" },
|
|
2714
|
+
pressed: { up: "hover", pick: "picked" },
|
|
2715
|
+
picked: { place: "placed", return: "rest" },
|
|
2716
|
+
placed: {}
|
|
2717
|
+
};
|
|
2718
|
+
var DEFAULT_TRANSITION = { duration: 0.35, ease: "power2.out" };
|
|
2719
|
+
function stripStates(config) {
|
|
2720
|
+
if (!config.states) return config;
|
|
2721
|
+
const { states: _states, ...rest } = config;
|
|
2722
|
+
return rest;
|
|
3196
2723
|
}
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
const
|
|
3206
|
-
const
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
// Adhesive backs skip it — the glue layer is its own bright surface.
|
|
3212
|
-
uBackDarken: {
|
|
3213
|
-
value: stock.adhesive ? 1 : 1 - Math.min(0.45, 0.12 + thickness * 0.9) * stock.opacity
|
|
3214
|
-
},
|
|
3215
|
-
uStockColor: { value: new THREE4.Color(stock.color) },
|
|
3216
|
-
uOpacity: { value: stock.opacity },
|
|
3217
|
-
uShowThrough: { value: showThrough },
|
|
3218
|
-
// Always compiled in: the shader early-outs at zero translucency, which
|
|
3219
|
-
// is cheaper than carrying a second program structure for it.
|
|
3220
|
-
...translucencyUniforms(surface.translucency ?? stock.translucency, lighting)
|
|
3221
|
-
};
|
|
3222
|
-
if (maps.hasFrontMap) uniforms.uFrontMap = { value: null };
|
|
3223
|
-
if (maps.hasBackMap) uniforms.uBackMap = { value: null };
|
|
3224
|
-
if (grain !== void 0 || banding > 0) {
|
|
3225
|
-
chunks.push(GRAIN_CHUNK);
|
|
3226
|
-
calls.push("plGrain(csm_DiffuseColor, csm_Roughness);");
|
|
3227
|
-
uniforms.uGrainAmount = { value: grain ?? 0 };
|
|
3228
|
-
uniforms.uGrainBanding = { value: banding };
|
|
2724
|
+
function resolveStateConfig(base, state) {
|
|
2725
|
+
const def = base.states?.states[state];
|
|
2726
|
+
const flat = stripStates(base);
|
|
2727
|
+
if (!def || Object.keys(def.overrides).length === 0) return flat;
|
|
2728
|
+
return paperConfigSchema.parse(mergeConfig(flat, def.overrides));
|
|
2729
|
+
}
|
|
2730
|
+
function pruneUndefined(value) {
|
|
2731
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
2732
|
+
const out = {};
|
|
2733
|
+
for (const [key, v] of Object.entries(value)) {
|
|
2734
|
+
if (v === void 0) continue;
|
|
2735
|
+
const pruned = pruneUndefined(v);
|
|
2736
|
+
const isEmptyObject = pruned !== null && typeof pruned === "object" && !Array.isArray(pruned) && Object.keys(pruned).length === 0;
|
|
2737
|
+
if (!isEmptyObject) out[key] = pruned;
|
|
3229
2738
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
2739
|
+
return out;
|
|
2740
|
+
}
|
|
2741
|
+
function recordStateOverride(config, stateName, patch) {
|
|
2742
|
+
const cleaned = pruneUndefined(patch);
|
|
2743
|
+
const states = config.states ?? paperStatesSchema.parse({});
|
|
2744
|
+
const def = states.states[stateName] ?? stateDefSchema.parse({});
|
|
2745
|
+
const overrides = mergeConfig(def.overrides, cleaned);
|
|
2746
|
+
return paperConfigSchema.parse({
|
|
2747
|
+
...config,
|
|
2748
|
+
states: {
|
|
2749
|
+
...states,
|
|
2750
|
+
states: { ...states.states, [stateName]: { ...def, overrides } }
|
|
2751
|
+
}
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
function flattenNumeric(value, prefix = "", out = {}) {
|
|
2755
|
+
if (typeof value === "number") {
|
|
2756
|
+
if (prefix) out[prefix] = value;
|
|
2757
|
+
return out;
|
|
3235
2758
|
}
|
|
3236
|
-
if (
|
|
3237
|
-
const
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
2759
|
+
if (value !== null && typeof value === "object") {
|
|
2760
|
+
const entries = Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value);
|
|
2761
|
+
for (const [key, v] of entries) {
|
|
2762
|
+
flattenNumeric(v, prefix ? `${prefix}.${key}` : key, out);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
return out;
|
|
2766
|
+
}
|
|
2767
|
+
function setPath(target, path, value) {
|
|
2768
|
+
const keys = path.split(".");
|
|
2769
|
+
let node = target;
|
|
2770
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
2771
|
+
const next = node[keys[i]];
|
|
2772
|
+
if (next === null || typeof next !== "object") return;
|
|
2773
|
+
node = next;
|
|
2774
|
+
}
|
|
2775
|
+
node[keys[keys.length - 1]] = value;
|
|
2776
|
+
}
|
|
2777
|
+
function applyFlat(target, flat) {
|
|
2778
|
+
for (const path in flat) setPath(target, path, flat[path]);
|
|
2779
|
+
}
|
|
2780
|
+
var clone = (v) => JSON.parse(JSON.stringify(v));
|
|
2781
|
+
var PaperStateMachine = class {
|
|
2782
|
+
state;
|
|
2783
|
+
base;
|
|
2784
|
+
opts;
|
|
2785
|
+
/** Structural target of the current state; numeric leaves live in `flat`. */
|
|
2786
|
+
structure;
|
|
2787
|
+
/**
|
|
2788
|
+
* The single live tween target — flattened numeric leaves of the config.
|
|
2789
|
+
* STABLE IDENTITY for the machine's lifetime: `goto`/`rebase` mutate it in
|
|
2790
|
+
* place (add/remove/keep keys) and never reassign, so an in-flight GSAP
|
|
2791
|
+
* tween keeps animating the same object across a rebase instead of freezing.
|
|
2792
|
+
*/
|
|
2793
|
+
flat = {};
|
|
2794
|
+
/** Mutable working config, polled by the consumer's frame loop via `liveConfig`. */
|
|
2795
|
+
live;
|
|
2796
|
+
tween = null;
|
|
2797
|
+
resolved = /* @__PURE__ */ new Map();
|
|
2798
|
+
constructor(base, opts = {}) {
|
|
2799
|
+
this.base = base;
|
|
2800
|
+
this.opts = opts;
|
|
2801
|
+
this.state = base.states?.initial ?? "rest";
|
|
2802
|
+
const target = this.resolve(this.state);
|
|
2803
|
+
this.structure = clone(target);
|
|
2804
|
+
this.live = clone(target);
|
|
2805
|
+
Object.assign(this.flat, flattenNumeric(target));
|
|
2806
|
+
}
|
|
2807
|
+
/** World-units drag distance that flips pressed → picked. */
|
|
2808
|
+
get pickThreshold() {
|
|
2809
|
+
return this.base.states?.pickThreshold ?? 0.1;
|
|
2810
|
+
}
|
|
2811
|
+
/**
|
|
2812
|
+
* The live config — the mutable working object with the current tween values
|
|
2813
|
+
* applied in place. Cheap (no allocation); meant to be polled every frame.
|
|
2814
|
+
* Never hand this to React; use `structuralConfig()` for an immutable snapshot.
|
|
2815
|
+
*/
|
|
2816
|
+
get liveConfig() {
|
|
2817
|
+
applyFlat(this.live, this.flat);
|
|
2818
|
+
return this.live;
|
|
2819
|
+
}
|
|
2820
|
+
/** Back-compat alias for `liveConfig` (tests, imperative reads). */
|
|
2821
|
+
get config() {
|
|
2822
|
+
return this.liveConfig;
|
|
2823
|
+
}
|
|
2824
|
+
/** True while a transition tween is in flight — consumers gate frame work on it. */
|
|
2825
|
+
get transitioning() {
|
|
2826
|
+
return this.tween !== null;
|
|
2827
|
+
}
|
|
2828
|
+
/** Exposed for tests: the in-flight transition tween, if any. */
|
|
2829
|
+
get activeTween() {
|
|
2830
|
+
return this.tween;
|
|
2831
|
+
}
|
|
2832
|
+
/** An immutable structural snapshot for React consumers (never the live object). */
|
|
2833
|
+
structuralConfig() {
|
|
2834
|
+
const out = clone(this.structure);
|
|
2835
|
+
applyFlat(out, this.flat);
|
|
2836
|
+
return out;
|
|
2837
|
+
}
|
|
2838
|
+
/** Fire a built-in trigger; returns the new state or null if it doesn't apply. */
|
|
2839
|
+
send(event) {
|
|
2840
|
+
const next = stateEventTransitions[this.state]?.[event];
|
|
2841
|
+
if (!next || next === this.state) return null;
|
|
2842
|
+
this.goto(next);
|
|
2843
|
+
return next;
|
|
2844
|
+
}
|
|
2845
|
+
/**
|
|
2846
|
+
* Drive to 'picked' through the legal chain (rest→hover→pressed→picked),
|
|
2847
|
+
* each hop instant so EVERY side effect fires (behavior override, backing
|
|
2848
|
+
* silhouette, onChange state reports, placed onEnter chain). This is the
|
|
2849
|
+
* keyboard/a11y entry point — it never produced pointer hover/press events,
|
|
2850
|
+
* so raw `send('pick')` from 'rest' was a no-op. Returns true if it landed.
|
|
2851
|
+
*/
|
|
2852
|
+
pickProgrammatic() {
|
|
2853
|
+
for (const event of ["enter", "down", "pick"]) {
|
|
2854
|
+
const next = stateEventTransitions[this.state]?.[event];
|
|
2855
|
+
if (next && next !== this.state) this.goto(next, { instant: true });
|
|
2856
|
+
}
|
|
2857
|
+
return this.state === "picked";
|
|
2858
|
+
}
|
|
2859
|
+
/** Instant, legal place (picked → placed) so onEnter/emit fires. */
|
|
2860
|
+
placeProgrammatic() {
|
|
2861
|
+
return this.driveInstant("place");
|
|
2862
|
+
}
|
|
2863
|
+
/** Instant, legal return (picked → rest). */
|
|
2864
|
+
returnProgrammatic() {
|
|
2865
|
+
return this.driveInstant("return");
|
|
2866
|
+
}
|
|
2867
|
+
driveInstant(event) {
|
|
2868
|
+
const next = stateEventTransitions[this.state]?.[event];
|
|
2869
|
+
if (!next || next === this.state) return false;
|
|
2870
|
+
this.goto(next, { instant: true });
|
|
2871
|
+
return true;
|
|
2872
|
+
}
|
|
2873
|
+
/** Transition to a state (escape hatch for custom states; `send` for triggers). */
|
|
2874
|
+
goto(state, opts) {
|
|
2875
|
+
const def = this.base.states?.states[state];
|
|
2876
|
+
const target = this.resolve(state);
|
|
2877
|
+
this.state = state;
|
|
2878
|
+
const targetFlat = flattenNumeric(target);
|
|
2879
|
+
this.structure = clone(target);
|
|
2880
|
+
this.live = clone(target);
|
|
2881
|
+
const changed = {};
|
|
2882
|
+
for (const path in this.flat) {
|
|
2883
|
+
if (!(path in targetFlat)) delete this.flat[path];
|
|
2884
|
+
}
|
|
2885
|
+
for (const [path, value] of Object.entries(targetFlat)) {
|
|
2886
|
+
const current = this.flat[path];
|
|
2887
|
+
if (current === void 0) this.flat[path] = value;
|
|
2888
|
+
else if (current !== value) changed[path] = value;
|
|
2889
|
+
}
|
|
2890
|
+
const duration = this.opts.instant || opts?.instant ? 0 : def?.transition.duration ?? DEFAULT_TRANSITION.duration;
|
|
2891
|
+
const ease = def?.transition.ease ?? DEFAULT_TRANSITION.ease;
|
|
2892
|
+
this.tween?.kill();
|
|
2893
|
+
this.tween = null;
|
|
2894
|
+
const arrive = () => {
|
|
2895
|
+
for (const [path, value] of Object.entries(changed)) this.flat[path] = value;
|
|
2896
|
+
this.emitStructure();
|
|
2897
|
+
for (const action of def?.onEnter ?? []) {
|
|
2898
|
+
if (action.startsWith("emit:")) this.opts.onAction?.(action.slice(5), state);
|
|
2899
|
+
}
|
|
3245
2900
|
};
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
2901
|
+
if (duration === 0 || Object.keys(changed).length === 0) {
|
|
2902
|
+
arrive();
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
this.emitStructure();
|
|
2906
|
+
this.tween = gsap.to(this.flat, {
|
|
2907
|
+
...changed,
|
|
2908
|
+
duration,
|
|
2909
|
+
ease,
|
|
2910
|
+
onComplete: () => {
|
|
2911
|
+
this.tween = null;
|
|
2912
|
+
arrive();
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
3249
2915
|
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
2916
|
+
/**
|
|
2917
|
+
* Swap the base config without resetting the machine — parameter edits and
|
|
2918
|
+
* runtime patches (torn perforation on detach) keep the current state and
|
|
2919
|
+
* live values instead of snapping back to `initial`. An in-flight tween is
|
|
2920
|
+
* left running on the SAME `flat` object, so the transition continues
|
|
2921
|
+
* smoothly to its target across the rebase (no freeze, no snap).
|
|
2922
|
+
*/
|
|
2923
|
+
rebase(base) {
|
|
2924
|
+
this.base = base;
|
|
2925
|
+
this.resolved.clear();
|
|
2926
|
+
const target = this.resolve(this.state);
|
|
2927
|
+
this.structure = clone(target);
|
|
2928
|
+
this.live = clone(target);
|
|
2929
|
+
const targetFlat = flattenNumeric(target);
|
|
2930
|
+
for (const path in this.flat) {
|
|
2931
|
+
if (!(path in targetFlat)) delete this.flat[path];
|
|
2932
|
+
}
|
|
2933
|
+
for (const [path, value] of Object.entries(targetFlat)) {
|
|
2934
|
+
if (!(path in this.flat)) this.flat[path] = value;
|
|
2935
|
+
}
|
|
2936
|
+
this.emitStructure();
|
|
3257
2937
|
}
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
uniforms.uAgingAmount = { value: aging };
|
|
2938
|
+
dispose() {
|
|
2939
|
+
this.tween?.kill();
|
|
2940
|
+
this.tween = null;
|
|
3262
2941
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
uniform float uOpacity;
|
|
3271
|
-
uniform float uShowThrough;
|
|
3272
|
-
${maps.hasFrontMap ? "uniform sampler2D uFrontMap;" : ""}
|
|
3273
|
-
${maps.hasBackMap && !stock.adhesive ? "uniform sampler2D uBackMap;" : ""}
|
|
3274
|
-
${TRANSLUCENCY_FRAGMENT}
|
|
3275
|
-
${chunks.join("\n")}
|
|
3276
|
-
void main() {
|
|
3277
|
-
vec3 front = ${frontExpr};
|
|
3278
|
-
if (gl_FrontFacing) {
|
|
3279
|
-
csm_DiffuseColor = vec4(front, uOpacity);
|
|
3280
|
-
} else {
|
|
3281
|
-
vec3 backBase = ${backBaseExpr};
|
|
3282
|
-
csm_DiffuseColor = vec4(backBase * mix(vec3(1.0), front, uShowThrough), uOpacity);
|
|
2942
|
+
resolve(state) {
|
|
2943
|
+
let config = this.resolved.get(state);
|
|
2944
|
+
if (!config) {
|
|
2945
|
+
config = resolveStateConfig(this.base, state);
|
|
2946
|
+
this.resolved.set(state, config);
|
|
2947
|
+
}
|
|
2948
|
+
return config;
|
|
3283
2949
|
}
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
2950
|
+
emitStructure() {
|
|
2951
|
+
this.opts.onChange?.(this.structuralConfig(), this.state);
|
|
2952
|
+
}
|
|
2953
|
+
};
|
|
2954
|
+
|
|
2955
|
+
// src/states/usePaperStates.ts
|
|
2956
|
+
import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
2957
|
+
function usePaperStates(config, enabled, instant, onAction, onStateChange) {
|
|
2958
|
+
const live = enabled && Boolean(config.states);
|
|
2959
|
+
const key = useMemo(() => JSON.stringify(config), [config]);
|
|
2960
|
+
const onActionRef = useRef(onAction);
|
|
2961
|
+
onActionRef.current = onAction;
|
|
2962
|
+
const onStateChangeRef = useRef(onStateChange);
|
|
2963
|
+
onStateChangeRef.current = onStateChange;
|
|
2964
|
+
const machineRef = useRef(null);
|
|
2965
|
+
const lastStateRef = useRef("rest");
|
|
2966
|
+
const [animated, setAnimated] = useState2(null);
|
|
2967
|
+
useEffect2(() => {
|
|
2968
|
+
if (!live) {
|
|
2969
|
+
machineRef.current?.dispose();
|
|
2970
|
+
machineRef.current = null;
|
|
2971
|
+
setAnimated(null);
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
if (machineRef.current) {
|
|
2975
|
+
machineRef.current.rebase(config);
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
const machine = new PaperStateMachine(config, {
|
|
2979
|
+
instant,
|
|
2980
|
+
// Structural boundaries only (not per tick) — safe to route to React.
|
|
2981
|
+
onChange: (c, state) => {
|
|
2982
|
+
if (state !== lastStateRef.current) {
|
|
2983
|
+
lastStateRef.current = state;
|
|
2984
|
+
onStateChangeRef.current?.(state);
|
|
2985
|
+
}
|
|
2986
|
+
setAnimated({ config: c, state });
|
|
2987
|
+
},
|
|
2988
|
+
onAction: (event, state) => onActionRef.current?.(event, state)
|
|
2989
|
+
});
|
|
2990
|
+
machineRef.current = machine;
|
|
2991
|
+
lastStateRef.current = machine.state;
|
|
2992
|
+
setAnimated({ config: machine.structuralConfig(), state: machine.state });
|
|
2993
|
+
}, [key, live, instant]);
|
|
2994
|
+
useEffect2(
|
|
2995
|
+
() => () => {
|
|
2996
|
+
machineRef.current?.dispose();
|
|
2997
|
+
machineRef.current = null;
|
|
2998
|
+
},
|
|
2999
|
+
[]
|
|
3291
3000
|
);
|
|
3292
3001
|
return {
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
creases ? "c" : "",
|
|
3297
|
-
aging !== void 0 ? "a" : "",
|
|
3298
|
-
perforation ? "p" : "",
|
|
3299
|
-
stock.adhesive ? "A" : ""
|
|
3300
|
-
].join("")}:${maps.hasFrontMap ? "F" : ""}${maps.hasBackMap ? "B" : ""}`,
|
|
3301
|
-
vertexShader: VERTEX,
|
|
3302
|
-
fragmentShader,
|
|
3303
|
-
uniforms,
|
|
3304
|
-
alphaTest: deckle || perforation ? 0.5 : 0
|
|
3002
|
+
config: live && animated ? animated.config : stripStates(config),
|
|
3003
|
+
state: live && animated ? animated.state : "rest",
|
|
3004
|
+
machine: live ? machineRef.current : null
|
|
3305
3005
|
};
|
|
3306
3006
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3007
|
+
|
|
3008
|
+
// src/PaperMesh.tsx
|
|
3009
|
+
import * as THREE7 from "three";
|
|
3010
|
+
import { gsap as gsap2 } from "gsap";
|
|
3011
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
3012
|
+
import { forwardRef, useEffect as useEffect5, useImperativeHandle, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
3013
|
+
|
|
3014
|
+
// src/core/normals.ts
|
|
3015
|
+
function computeSheetNormals(geometry) {
|
|
3016
|
+
const index = geometry.index;
|
|
3017
|
+
const normalAttr = geometry.attributes.normal;
|
|
3018
|
+
const positionAttr = geometry.attributes.position;
|
|
3019
|
+
if (!index || !normalAttr || !positionAttr) {
|
|
3020
|
+
geometry.computeVertexNormals();
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
const pos = positionAttr.array;
|
|
3024
|
+
const nrm = normalAttr.array;
|
|
3025
|
+
const idx = index.array;
|
|
3026
|
+
nrm.fill(0);
|
|
3027
|
+
for (let i = 0, l = idx.length; i < l; i += 3) {
|
|
3028
|
+
const a = idx[i] * 3;
|
|
3029
|
+
const b = idx[i + 1] * 3;
|
|
3030
|
+
const c = idx[i + 2] * 3;
|
|
3031
|
+
const bx = pos[b];
|
|
3032
|
+
const by = pos[b + 1];
|
|
3033
|
+
const bz = pos[b + 2];
|
|
3034
|
+
const cbx = pos[c] - bx;
|
|
3035
|
+
const cby = pos[c + 1] - by;
|
|
3036
|
+
const cbz = pos[c + 2] - bz;
|
|
3037
|
+
const abx = pos[a] - bx;
|
|
3038
|
+
const aby = pos[a + 1] - by;
|
|
3039
|
+
const abz = pos[a + 2] - bz;
|
|
3040
|
+
const nx = cby * abz - cbz * aby;
|
|
3041
|
+
const ny = cbz * abx - cbx * abz;
|
|
3042
|
+
const nz = cbx * aby - cby * abx;
|
|
3043
|
+
nrm[a] = nrm[a] + nx;
|
|
3044
|
+
nrm[a + 1] = nrm[a + 1] + ny;
|
|
3045
|
+
nrm[a + 2] = nrm[a + 2] + nz;
|
|
3046
|
+
nrm[b] = nrm[b] + nx;
|
|
3047
|
+
nrm[b + 1] = nrm[b + 1] + ny;
|
|
3048
|
+
nrm[b + 2] = nrm[b + 2] + nz;
|
|
3049
|
+
nrm[c] = nrm[c] + nx;
|
|
3050
|
+
nrm[c + 1] = nrm[c + 1] + ny;
|
|
3051
|
+
nrm[c + 2] = nrm[c + 2] + nz;
|
|
3052
|
+
}
|
|
3053
|
+
for (let i = 0, l = nrm.length; i < l; i += 3) {
|
|
3054
|
+
const x = nrm[i];
|
|
3055
|
+
const y = nrm[i + 1];
|
|
3056
|
+
const z26 = nrm[i + 2];
|
|
3057
|
+
const len = Math.sqrt(x * x + y * y + z26 * z26) || 1;
|
|
3058
|
+
nrm[i] = x / len;
|
|
3059
|
+
nrm[i + 1] = y / len;
|
|
3060
|
+
nrm[i + 2] = z26 / len;
|
|
3061
|
+
}
|
|
3062
|
+
normalAttr.needsUpdate = true;
|
|
3311
3063
|
}
|
|
3312
3064
|
|
|
3313
|
-
// src/
|
|
3314
|
-
import {
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
return
|
|
3065
|
+
// src/core/stable.ts
|
|
3066
|
+
import { useRef as useRef2 } from "react";
|
|
3067
|
+
function useStable(value) {
|
|
3068
|
+
const held = useRef2(value);
|
|
3069
|
+
if (!deepEqual(held.current, value)) held.current = value;
|
|
3070
|
+
return held.current;
|
|
3319
3071
|
}
|
|
3320
|
-
function
|
|
3321
|
-
|
|
3072
|
+
function deepEqual(a, b) {
|
|
3073
|
+
if (Object.is(a, b)) return true;
|
|
3074
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
3075
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
3076
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
3077
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
3078
|
+
return true;
|
|
3079
|
+
}
|
|
3080
|
+
const left = a;
|
|
3081
|
+
const right = b;
|
|
3082
|
+
const keys = /* @__PURE__ */ new Set();
|
|
3083
|
+
for (const key of Object.keys(left)) if (left[key] !== void 0) keys.add(key);
|
|
3084
|
+
for (const key of Object.keys(right)) if (right[key] !== void 0) keys.add(key);
|
|
3085
|
+
for (const key of keys) if (!deepEqual(left[key], right[key])) return false;
|
|
3086
|
+
return true;
|
|
3322
3087
|
}
|
|
3323
3088
|
|
|
3324
|
-
// src/
|
|
3325
|
-
import * as
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
lighting = "studio"
|
|
3337
|
-
}) {
|
|
3338
|
-
const rig = useLightRig(lighting);
|
|
3339
|
-
const composed = composeSurface(
|
|
3340
|
-
surface,
|
|
3341
|
-
stock,
|
|
3342
|
-
thickness,
|
|
3343
|
-
{
|
|
3344
|
-
hasFrontMap: Boolean(texture),
|
|
3345
|
-
hasBackMap: Boolean(backTexture)
|
|
3346
|
-
},
|
|
3347
|
-
sheet2,
|
|
3348
|
-
rig
|
|
3349
|
-
);
|
|
3350
|
-
const bound = useMemo(() => composed.uniforms, [composed.structureKey]);
|
|
3351
|
-
useEffect(() => {
|
|
3352
|
-
for (const [key, uniform] of Object.entries(composed.uniforms)) {
|
|
3353
|
-
if (!bound[key] || key === "uFrontMap" || key === "uBackMap") continue;
|
|
3354
|
-
if (bound[key].value instanceof THREE5.Color && uniform.value instanceof THREE5.Color) {
|
|
3355
|
-
;
|
|
3356
|
-
bound[key].value.copy(uniform.value);
|
|
3357
|
-
} else {
|
|
3358
|
-
bound[key].value = uniform.value;
|
|
3359
|
-
}
|
|
3360
|
-
}
|
|
3361
|
-
});
|
|
3362
|
-
useEffect(() => {
|
|
3363
|
-
if (bound.uFrontMap) bound.uFrontMap.value = texture;
|
|
3364
|
-
if (bound.uBackMap) bound.uBackMap.value = backTexture ?? null;
|
|
3365
|
-
}, [bound, texture, backTexture]);
|
|
3366
|
-
return /* @__PURE__ */ jsx2(
|
|
3367
|
-
CustomShaderMaterial,
|
|
3368
|
-
{
|
|
3369
|
-
baseMaterial: THREE5.MeshStandardMaterial,
|
|
3370
|
-
vertexShader: composed.vertexShader,
|
|
3371
|
-
fragmentShader: composed.fragmentShader,
|
|
3372
|
-
uniforms: bound,
|
|
3373
|
-
color: "#ffffff",
|
|
3374
|
-
roughness: stock.roughness,
|
|
3375
|
-
metalness: 0,
|
|
3376
|
-
transparent: stock.opacity < 1,
|
|
3377
|
-
opacity: stock.opacity,
|
|
3378
|
-
alphaTest: composed.alphaTest,
|
|
3379
|
-
side: THREE5.DoubleSide
|
|
3380
|
-
},
|
|
3381
|
-
composed.structureKey
|
|
3382
|
-
);
|
|
3089
|
+
// src/core/sheet.ts
|
|
3090
|
+
import * as THREE from "three";
|
|
3091
|
+
function resolveSegments(sheet2, minSegments = 2, autoSegments = LEGACY_FLAT_SEGMENTS) {
|
|
3092
|
+
const [minX, minY] = typeof minSegments === "number" ? [minSegments, minSegments] : minSegments;
|
|
3093
|
+
if (sheet2.segments !== "auto") {
|
|
3094
|
+
return [Math.max(sheet2.segments, minX, 2), Math.max(sheet2.segments, minY, 2)];
|
|
3095
|
+
}
|
|
3096
|
+
const [wantX, wantY] = typeof autoSegments === "number" ? spreadByAspect(sheet2, quantizeSegments(Math.max(autoSegments, FLAT_SEGMENTS))) : [
|
|
3097
|
+
quantizeSegments(Math.max(wantOrFlat(autoSegments[0]), FLAT_SEGMENTS)),
|
|
3098
|
+
quantizeSegments(Math.max(wantOrFlat(autoSegments[1]), FLAT_SEGMENTS))
|
|
3099
|
+
];
|
|
3100
|
+
return [Math.max(wantX, minX, 2), Math.max(wantY, minY, 2)];
|
|
3383
3101
|
}
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
const [system, setSystem] = useState(
|
|
3390
|
-
() => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
3391
|
-
);
|
|
3392
|
-
useEffect2(() => {
|
|
3393
|
-
const query = window.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
3394
|
-
if (!query) return;
|
|
3395
|
-
const onChange = () => setSystem(query.matches);
|
|
3396
|
-
query.addEventListener("change", onChange);
|
|
3397
|
-
return () => query.removeEventListener("change", onChange);
|
|
3398
|
-
}, []);
|
|
3399
|
-
return override ?? system;
|
|
3102
|
+
var wantOrFlat = (n) => Number.isFinite(n) ? n : FLAT_SEGMENTS;
|
|
3103
|
+
function spreadByAspect(sheet2, target) {
|
|
3104
|
+
const long = Math.max(sheet2.width, sheet2.height);
|
|
3105
|
+
if (!(long > 0)) return [target, target];
|
|
3106
|
+
return [Math.round(sheet2.width / long * target), Math.round(sheet2.height / long * target)];
|
|
3400
3107
|
}
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3108
|
+
function createSheetGeometry(sheet2, minSegments = 2, autoSegments = LEGACY_FLAT_SEGMENTS) {
|
|
3109
|
+
const [sx, sy] = resolveSegments(sheet2, minSegments, autoSegments);
|
|
3110
|
+
return new THREE.PlaneGeometry(sheet2.width, sheet2.height, sx, sy);
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
// src/content/texture.ts
|
|
3114
|
+
import * as THREE2 from "three";
|
|
3115
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
3116
|
+
|
|
3117
|
+
// src/content/card.ts
|
|
3118
|
+
var TITLE_RATIO = 0.52;
|
|
3119
|
+
var NOTE_RATIO = 0.46;
|
|
3120
|
+
var TITLE_TRACKING = 0.16;
|
|
3121
|
+
var NOTE_TRACKING = 0.06;
|
|
3122
|
+
function paintCard(ctx, w, h, content, stock, dpr) {
|
|
3123
|
+
const ink = content.color === "#2b2620" ? stock.inkColor : content.color;
|
|
3124
|
+
const size = content.size * dpr;
|
|
3125
|
+
const pad = content.padding * Math.min(w, h);
|
|
3126
|
+
const maxWidth = w - pad * 2;
|
|
3127
|
+
const x = content.align === "center" ? w / 2 : pad;
|
|
3128
|
+
ctx.textAlign = content.align === "center" ? "center" : "left";
|
|
3129
|
+
ctx.textBaseline = "alphabetic";
|
|
3130
|
+
const titleSize = size * TITLE_RATIO;
|
|
3131
|
+
const noteSize = size * NOTE_RATIO;
|
|
3132
|
+
const bodyStep = size * 1.35;
|
|
3133
|
+
const bodyLines = content.body ? wrapLines(ctx, content.body, maxWidth, `${size}px ${content.font}`) : [];
|
|
3134
|
+
const titleBlock = content.title ? titleSize * 1.9 : 0;
|
|
3135
|
+
const ruleBlock = content.rule && content.title ? titleSize * 0.9 : 0;
|
|
3136
|
+
const noteBlock = content.note ? noteSize * 2.4 : 0;
|
|
3137
|
+
const bodyBlock = bodyLines.length * bodyStep;
|
|
3138
|
+
const total = titleBlock + ruleBlock + bodyBlock + noteBlock;
|
|
3139
|
+
let y = Math.max(pad, (h - total) / 2) + size * 0.9;
|
|
3140
|
+
if (content.title) {
|
|
3141
|
+
ctx.font = `${titleSize}px ${content.font}`;
|
|
3142
|
+
ctx.letterSpacing = `${TITLE_TRACKING}em`;
|
|
3143
|
+
ctx.fillStyle = ink;
|
|
3144
|
+
ctx.globalAlpha = 0.72;
|
|
3145
|
+
ctx.fillText(content.title.toUpperCase(), x, y - size * 0.5);
|
|
3146
|
+
ctx.globalAlpha = 1;
|
|
3147
|
+
ctx.letterSpacing = "0em";
|
|
3148
|
+
y += titleBlock - size * 0.5;
|
|
3149
|
+
if (content.rule) {
|
|
3150
|
+
ctx.save();
|
|
3151
|
+
ctx.strokeStyle = ink;
|
|
3152
|
+
ctx.globalAlpha = 0.28;
|
|
3153
|
+
ctx.lineWidth = Math.max(1, dpr * 0.75);
|
|
3154
|
+
ctx.beginPath();
|
|
3155
|
+
ctx.moveTo(content.align === "center" ? w / 2 - maxWidth / 2 : pad, y - titleSize * 0.5);
|
|
3156
|
+
ctx.lineTo(content.align === "center" ? w / 2 + maxWidth / 2 : pad + maxWidth, y - titleSize * 0.5);
|
|
3157
|
+
ctx.stroke();
|
|
3158
|
+
ctx.restore();
|
|
3159
|
+
y += ruleBlock;
|
|
3160
|
+
}
|
|
3409
3161
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3162
|
+
if (content.ruled && bodyLines.length > 0) {
|
|
3163
|
+
ctx.save();
|
|
3164
|
+
ctx.strokeStyle = ink;
|
|
3165
|
+
ctx.globalAlpha = 0.14;
|
|
3166
|
+
ctx.lineWidth = Math.max(1, dpr * 0.6);
|
|
3167
|
+
for (let i = 0; i < bodyLines.length; i++) {
|
|
3168
|
+
const lineY = y + i * bodyStep + size * 0.28;
|
|
3169
|
+
ctx.beginPath();
|
|
3170
|
+
ctx.moveTo(pad, lineY);
|
|
3171
|
+
ctx.lineTo(pad + maxWidth, lineY);
|
|
3172
|
+
ctx.stroke();
|
|
3173
|
+
}
|
|
3174
|
+
ctx.restore();
|
|
3175
|
+
}
|
|
3176
|
+
ctx.font = `${size}px ${content.font}`;
|
|
3177
|
+
ctx.fillStyle = ink;
|
|
3178
|
+
for (const line of bodyLines) {
|
|
3179
|
+
if (y > h - pad) break;
|
|
3180
|
+
ctx.fillText(line, x, y);
|
|
3181
|
+
y += bodyStep;
|
|
3182
|
+
}
|
|
3183
|
+
if (content.note) {
|
|
3184
|
+
ctx.font = `${noteSize}px ${content.font}`;
|
|
3185
|
+
ctx.letterSpacing = `${NOTE_TRACKING}em`;
|
|
3186
|
+
ctx.globalAlpha = 0.6;
|
|
3187
|
+
ctx.fillText(content.note, x, Math.min(y + noteSize * 1.4, h - pad));
|
|
3188
|
+
ctx.globalAlpha = 1;
|
|
3189
|
+
ctx.letterSpacing = "0em";
|
|
3420
3190
|
}
|
|
3421
|
-
return "A blank sheet of paper.";
|
|
3422
3191
|
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
border: 0
|
|
3433
|
-
};
|
|
3434
|
-
function PaperMirror({ config }) {
|
|
3435
|
-
return /* @__PURE__ */ jsx3("div", { style: visuallyHidden, "aria-hidden": false, children: contentText(config) });
|
|
3192
|
+
|
|
3193
|
+
// src/content/texture.ts
|
|
3194
|
+
var LONG_EDGE = 1024;
|
|
3195
|
+
var DPR = 2;
|
|
3196
|
+
function contentCanvasSize(sheet2) {
|
|
3197
|
+
const long = Math.max(sheet2.width, sheet2.height);
|
|
3198
|
+
const w = Math.round(sheet2.width / long * LONG_EDGE * DPR);
|
|
3199
|
+
const h = Math.round(sheet2.height / long * LONG_EDGE * DPR);
|
|
3200
|
+
return [w, h];
|
|
3436
3201
|
}
|
|
3437
|
-
function
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
return /* @__PURE__ */ jsx3(
|
|
3441
|
-
"div",
|
|
3442
|
-
{
|
|
3443
|
-
role: "img",
|
|
3444
|
-
"aria-label": contentText(config),
|
|
3445
|
-
style: {
|
|
3446
|
-
width: "100%",
|
|
3447
|
-
height: "100%",
|
|
3448
|
-
display: "grid",
|
|
3449
|
-
placeItems: "center",
|
|
3450
|
-
background: "transparent"
|
|
3451
|
-
},
|
|
3452
|
-
children: /* @__PURE__ */ jsx3(
|
|
3453
|
-
"div",
|
|
3454
|
-
{
|
|
3455
|
-
style: {
|
|
3456
|
-
aspectRatio: `${config.sheet.width} / ${config.sheet.height}`,
|
|
3457
|
-
maxWidth: "80%",
|
|
3458
|
-
maxHeight: "90%",
|
|
3459
|
-
background: stock.color,
|
|
3460
|
-
color: stock.inkColor,
|
|
3461
|
-
boxShadow: "0 6px 24px rgba(0,0,0,0.25)",
|
|
3462
|
-
padding: "8%",
|
|
3463
|
-
overflow: "hidden",
|
|
3464
|
-
fontFamily: config.content.type === "receipt" ? "ui-monospace, monospace" : "Georgia, serif",
|
|
3465
|
-
whiteSpace: "pre-wrap",
|
|
3466
|
-
fontSize: 14
|
|
3467
|
-
},
|
|
3468
|
-
children: isImage && config.content.type === "image" ? /* @__PURE__ */ jsx3(
|
|
3469
|
-
"img",
|
|
3470
|
-
{
|
|
3471
|
-
src: config.content.src,
|
|
3472
|
-
alt: config.content.alt ?? "",
|
|
3473
|
-
style: { width: "100%", height: "100%", objectFit: "cover", margin: "-8%" }
|
|
3474
|
-
}
|
|
3475
|
-
) : contentText(config)
|
|
3476
|
-
}
|
|
3477
|
-
)
|
|
3478
|
-
}
|
|
3479
|
-
);
|
|
3202
|
+
function paintBackground(ctx, w, h, stock) {
|
|
3203
|
+
ctx.fillStyle = stock.color;
|
|
3204
|
+
ctx.fillRect(0, 0, w, h);
|
|
3480
3205
|
}
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3206
|
+
function paintImage(ctx, w, h, img, fit) {
|
|
3207
|
+
const scale = fit === "cover" ? Math.max(w / img.width, h / img.height) : Math.min(w / img.width, h / img.height);
|
|
3208
|
+
const dw = img.width * scale;
|
|
3209
|
+
const dh = img.height * scale;
|
|
3210
|
+
ctx.drawImage(img, (w - dw) / 2, (h - dh) / 2, dw, dh);
|
|
3486
3211
|
}
|
|
3487
|
-
function
|
|
3488
|
-
const
|
|
3489
|
-
|
|
3212
|
+
function paintText(ctx, w, h, content, stock) {
|
|
3213
|
+
const size = content.size * DPR;
|
|
3214
|
+
const pad = content.padding * Math.min(w, h);
|
|
3215
|
+
const font = `${content.weight} ${size}px ${content.font}`;
|
|
3216
|
+
ctx.font = font;
|
|
3217
|
+
ctx.fillStyle = content.color === "#2b2620" ? stock.inkColor : content.color;
|
|
3218
|
+
ctx.textBaseline = "top";
|
|
3219
|
+
ctx.textAlign = content.align;
|
|
3220
|
+
ctx.letterSpacing = `${content.tracking}em`;
|
|
3221
|
+
const maxWidth = w - pad * 2;
|
|
3222
|
+
const x = content.align === "left" ? pad : content.align === "right" ? w - pad : w / 2;
|
|
3223
|
+
const lineStep = size * content.lineHeight;
|
|
3224
|
+
const lines = wrapLines(ctx, content.text, maxWidth, font);
|
|
3225
|
+
ctx.font = font;
|
|
3226
|
+
ctx.letterSpacing = `${content.tracking}em`;
|
|
3227
|
+
const block = lines.length * lineStep;
|
|
3228
|
+
let y = content.valign === "center" ? Math.max(pad, (h - block) / 2) : pad;
|
|
3229
|
+
for (const line of lines) {
|
|
3230
|
+
if (y > h - pad) break;
|
|
3231
|
+
ctx.fillText(line, x, y);
|
|
3232
|
+
y += lineStep;
|
|
3233
|
+
}
|
|
3234
|
+
ctx.letterSpacing = "0em";
|
|
3490
3235
|
}
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
if (!config.states) return config;
|
|
3504
|
-
const { states: _states, ...rest } = config;
|
|
3505
|
-
return rest;
|
|
3236
|
+
function renderContentToCanvas(content, sheet2, stock, image) {
|
|
3237
|
+
const [w, h] = contentCanvasSize(sheet2);
|
|
3238
|
+
const canvas = document.createElement("canvas");
|
|
3239
|
+
canvas.width = w;
|
|
3240
|
+
canvas.height = h;
|
|
3241
|
+
const ctx = canvas.getContext("2d");
|
|
3242
|
+
paintBackground(ctx, w, h, stock);
|
|
3243
|
+
if (content.type === "image" && image && content.src) paintImage(ctx, w, h, image, content.fit);
|
|
3244
|
+
if (content.type === "text") paintText(ctx, w, h, content, stock);
|
|
3245
|
+
if (content.type === "receipt") paintReceipt(ctx, w, h, content, stock);
|
|
3246
|
+
if (content.type === "card") paintCard(ctx, w, h, content, stock, DPR);
|
|
3247
|
+
return canvas;
|
|
3506
3248
|
}
|
|
3507
|
-
function
|
|
3508
|
-
const
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3249
|
+
function makeTexture(canvas) {
|
|
3250
|
+
const tex = new THREE2.CanvasTexture(canvas);
|
|
3251
|
+
tex.colorSpace = THREE2.SRGBColorSpace;
|
|
3252
|
+
tex.anisotropy = 8;
|
|
3253
|
+
tex.generateMipmaps = true;
|
|
3254
|
+
return tex;
|
|
3512
3255
|
}
|
|
3513
|
-
function
|
|
3514
|
-
|
|
3515
|
-
const
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3256
|
+
function useContentTexture(content, sheet2, stock) {
|
|
3257
|
+
const [texture, setTexture] = useState3(null);
|
|
3258
|
+
const key = JSON.stringify({ content: content ?? null, w: sheet2.width, h: sheet2.height, stock: stock.id });
|
|
3259
|
+
useEffect3(() => {
|
|
3260
|
+
let disposed = false;
|
|
3261
|
+
let tex = null;
|
|
3262
|
+
if (!content) {
|
|
3263
|
+
setTexture(null);
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
const commit = (canvas) => {
|
|
3267
|
+
if (disposed) return;
|
|
3268
|
+
tex = makeTexture(canvas);
|
|
3269
|
+
setTexture(tex);
|
|
3270
|
+
};
|
|
3271
|
+
if (content.type === "image" && content.src) {
|
|
3272
|
+
const img = new Image();
|
|
3273
|
+
img.crossOrigin = "anonymous";
|
|
3274
|
+
img.onload = () => commit(renderContentToCanvas(content, sheet2, stock, img));
|
|
3275
|
+
img.onerror = () => commit(renderContentToCanvas(content, sheet2, stock));
|
|
3276
|
+
img.src = content.src;
|
|
3277
|
+
} else if (content.type === "text" || content.type === "card") {
|
|
3278
|
+
void ensureFont(content.font, content.size * DPR).then(
|
|
3279
|
+
() => commit(renderContentToCanvas(content, sheet2, stock))
|
|
3280
|
+
);
|
|
3281
|
+
} else if (content.type === "receipt") {
|
|
3282
|
+
document.fonts.ready.then(() => commit(renderContentToCanvas(content, sheet2, stock)));
|
|
3283
|
+
} else {
|
|
3284
|
+
commit(renderContentToCanvas(content, sheet2, stock));
|
|
3285
|
+
}
|
|
3286
|
+
return () => {
|
|
3287
|
+
disposed = true;
|
|
3288
|
+
tex?.dispose();
|
|
3289
|
+
};
|
|
3290
|
+
}, [key]);
|
|
3291
|
+
return texture;
|
|
3523
3292
|
}
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3293
|
+
|
|
3294
|
+
// src/deformers/compose.ts
|
|
3295
|
+
import * as THREE3 from "three";
|
|
3296
|
+
var scratchPos = new THREE3.Vector3();
|
|
3297
|
+
var scratchUv = new THREE3.Vector2();
|
|
3298
|
+
var activeFns = [];
|
|
3299
|
+
var activeOptions = [];
|
|
3300
|
+
function applyDeformerStack(geometry, basePositions, stack, ctx) {
|
|
3301
|
+
const position = geometry.attributes.position;
|
|
3302
|
+
const uv = geometry.attributes.uv;
|
|
3303
|
+
const array = position.array;
|
|
3304
|
+
const uvArray = uv.array;
|
|
3305
|
+
const count = position.count;
|
|
3306
|
+
activeFns.length = 0;
|
|
3307
|
+
activeOptions.length = 0;
|
|
3308
|
+
for (const instance of stack) {
|
|
3309
|
+
if (instance.enabled === false) continue;
|
|
3310
|
+
activeFns.push(getDeformer(instance.type).displace);
|
|
3311
|
+
activeOptions.push(instance.options);
|
|
3312
|
+
}
|
|
3313
|
+
array.set(basePositions);
|
|
3314
|
+
for (let k = 0; k < activeFns.length; k++) {
|
|
3315
|
+
const displace = activeFns[k];
|
|
3316
|
+
const options = activeOptions[k];
|
|
3317
|
+
for (let v = 0; v < count; v++) {
|
|
3318
|
+
const i3 = v * 3;
|
|
3319
|
+
const i2 = v * 2;
|
|
3320
|
+
scratchPos.set(array[i3], array[i3 + 1], array[i3 + 2]);
|
|
3321
|
+
scratchUv.set(uvArray[i2], uvArray[i2 + 1]);
|
|
3322
|
+
displace(scratchPos, scratchUv, options, ctx);
|
|
3323
|
+
array[i3] = scratchPos.x;
|
|
3324
|
+
array[i3 + 1] = scratchPos.y;
|
|
3325
|
+
array[i3 + 2] = scratchPos.z;
|
|
3534
3326
|
}
|
|
3535
|
-
}
|
|
3327
|
+
}
|
|
3328
|
+
position.needsUpdate = true;
|
|
3329
|
+
computeSheetNormals(geometry);
|
|
3536
3330
|
}
|
|
3537
|
-
function
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3331
|
+
function displacePoint(point, uvX, uvY, stack, ctx) {
|
|
3332
|
+
scratchUv.set(uvX, uvY);
|
|
3333
|
+
for (const instance of stack) {
|
|
3334
|
+
if (instance.enabled === false) continue;
|
|
3335
|
+
getDeformer(instance.type).displace(point, scratchUv, instance.options, ctx);
|
|
3541
3336
|
}
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3337
|
+
return point;
|
|
3338
|
+
}
|
|
3339
|
+
function stackMinSegments(stack, sheet2) {
|
|
3340
|
+
const out = [2, 2];
|
|
3341
|
+
for (const instance of stack) {
|
|
3342
|
+
const deformer = getDeformer(instance.type);
|
|
3343
|
+
const floor = deformer.geometry?.minSegments;
|
|
3344
|
+
if (!floor) continue;
|
|
3345
|
+
take(out, deformer, instance.options, sheet2, floor);
|
|
3547
3346
|
}
|
|
3548
3347
|
return out;
|
|
3549
3348
|
}
|
|
3550
|
-
function
|
|
3551
|
-
const
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
const
|
|
3555
|
-
|
|
3556
|
-
|
|
3349
|
+
function stackAutoSegments(stack, sheet2) {
|
|
3350
|
+
const out = [0, 0];
|
|
3351
|
+
for (const instance of stack) {
|
|
3352
|
+
if (instance.enabled === false) continue;
|
|
3353
|
+
const deformer = getDeformer(instance.type);
|
|
3354
|
+
const geometry = deformer.geometry;
|
|
3355
|
+
if (!geometry) continue;
|
|
3356
|
+
const want = geometry.autoSegments ? geometry.autoSegments(instance.options, sheet2) : geometry.minSegments ?? 0;
|
|
3357
|
+
take(out, deformer, instance.options, sheet2, want);
|
|
3557
3358
|
}
|
|
3558
|
-
|
|
3359
|
+
return out;
|
|
3559
3360
|
}
|
|
3560
|
-
function
|
|
3561
|
-
|
|
3361
|
+
function take(out, deformer, options, sheet2, demand) {
|
|
3362
|
+
const declared = deformer.geometry?.axis?.(options, sheet2);
|
|
3363
|
+
const angle = typeof declared === "number" && Number.isFinite(declared) ? declared : null;
|
|
3364
|
+
const [x, y] = axialSegments(sheet2, angle, demand);
|
|
3365
|
+
if (x > out[0]) out[0] = x;
|
|
3366
|
+
if (y > out[1]) out[1] = y;
|
|
3562
3367
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
this.
|
|
3587
|
-
this.
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3368
|
+
|
|
3369
|
+
// src/physics/cloth.ts
|
|
3370
|
+
var FIXED_DT = 1 / 120;
|
|
3371
|
+
var SOLVER_ITERATIONS = 5;
|
|
3372
|
+
var SLEEP_EPSILON = 1e-6;
|
|
3373
|
+
var SLEEP_FRAMES = 45;
|
|
3374
|
+
var ClothSim = class {
|
|
3375
|
+
cols;
|
|
3376
|
+
rows;
|
|
3377
|
+
count;
|
|
3378
|
+
positions;
|
|
3379
|
+
prev;
|
|
3380
|
+
pinned;
|
|
3381
|
+
pinTargets;
|
|
3382
|
+
constraints = [];
|
|
3383
|
+
params;
|
|
3384
|
+
time = 0;
|
|
3385
|
+
accumulator = 0;
|
|
3386
|
+
stillFrames = 0;
|
|
3387
|
+
grabbedIndex = -1;
|
|
3388
|
+
/** True when the sim has settled and steps are skipped. */
|
|
3389
|
+
asleep = false;
|
|
3390
|
+
constructor(cols, rows, width, height, pins, params) {
|
|
3391
|
+
this.cols = cols;
|
|
3392
|
+
this.rows = rows;
|
|
3393
|
+
this.count = cols * rows;
|
|
3394
|
+
this.params = { ...params };
|
|
3395
|
+
this.positions = new Float32Array(this.count * 3);
|
|
3396
|
+
this.prev = new Float32Array(this.count * 3);
|
|
3397
|
+
this.pinned = new Uint8Array(this.count);
|
|
3398
|
+
this.pinTargets = new Float32Array(this.count * 3);
|
|
3399
|
+
for (let r = 0; r < rows; r++) {
|
|
3400
|
+
for (let c = 0; c < cols; c++) {
|
|
3401
|
+
const i3 = (r * cols + c) * 3;
|
|
3402
|
+
this.positions[i3] = (c / (cols - 1) - 0.5) * width;
|
|
3403
|
+
this.positions[i3 + 1] = (0.5 - r / (rows - 1)) * height;
|
|
3404
|
+
this.positions[i3 + 2] = 0;
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
this.prev.set(this.positions);
|
|
3408
|
+
const idx = (r, c) => r * cols + c;
|
|
3409
|
+
const link = (a, b, kind) => {
|
|
3410
|
+
const dx = this.positions[a * 3] - this.positions[b * 3];
|
|
3411
|
+
const dy = this.positions[a * 3 + 1] - this.positions[b * 3 + 1];
|
|
3412
|
+
this.constraints.push({ a, b, rest: Math.hypot(dx, dy), kind });
|
|
3413
|
+
};
|
|
3414
|
+
for (let r = 0; r < rows; r++) {
|
|
3415
|
+
for (let c = 0; c < cols; c++) {
|
|
3416
|
+
if (c + 1 < cols) link(idx(r, c), idx(r, c + 1), 0);
|
|
3417
|
+
if (r + 1 < rows) link(idx(r, c), idx(r + 1, c), 0);
|
|
3418
|
+
if (c + 1 < cols && r + 1 < rows) {
|
|
3419
|
+
link(idx(r, c), idx(r + 1, c + 1), 1);
|
|
3420
|
+
link(idx(r, c + 1), idx(r + 1, c), 1);
|
|
3421
|
+
}
|
|
3422
|
+
if (c + 2 < cols) link(idx(r, c), idx(r, c + 2), 2);
|
|
3423
|
+
if (r + 2 < rows) link(idx(r, c), idx(r + 2, c), 2);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
const pin = (r, c) => {
|
|
3427
|
+
const i = idx(r, c);
|
|
3428
|
+
this.pinned[i] = 1;
|
|
3429
|
+
this.pinTargets.set(this.positions.subarray(i * 3, i * 3 + 3), i * 3);
|
|
3430
|
+
};
|
|
3431
|
+
if (pins === "top-edge") for (let c = 0; c < cols; c++) pin(0, c);
|
|
3432
|
+
if (pins === "top-corners") {
|
|
3433
|
+
pin(0, 0);
|
|
3434
|
+
pin(0, cols - 1);
|
|
3435
|
+
}
|
|
3436
|
+
if (pins === "corner") pin(0, 0);
|
|
3614
3437
|
}
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
const
|
|
3618
|
-
|
|
3619
|
-
|
|
3438
|
+
setParams(params) {
|
|
3439
|
+
let changed = false;
|
|
3440
|
+
for (const key of ["stiffness", "gravity", "wind", "floor"]) {
|
|
3441
|
+
const value = params[key];
|
|
3442
|
+
if (value !== void 0 && value !== this.params[key]) {
|
|
3443
|
+
this.params[key] = value;
|
|
3444
|
+
changed = true;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
if (changed) this.wake();
|
|
3620
3448
|
}
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
if (!next || next === this.state) return null;
|
|
3625
|
-
this.goto(next);
|
|
3626
|
-
return next;
|
|
3449
|
+
wake() {
|
|
3450
|
+
this.asleep = false;
|
|
3451
|
+
this.stillFrames = 0;
|
|
3627
3452
|
}
|
|
3628
|
-
/**
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3453
|
+
/** Nearest particle to a local-space point — the grab interface. */
|
|
3454
|
+
grabNearest(x, y, z26) {
|
|
3455
|
+
let best = -1;
|
|
3456
|
+
let bestDist = Infinity;
|
|
3457
|
+
for (let i = 0; i < this.count; i++) {
|
|
3458
|
+
const dx = this.positions[i * 3] - x;
|
|
3459
|
+
const dy = this.positions[i * 3 + 1] - y;
|
|
3460
|
+
const dz = this.positions[i * 3 + 2] - z26;
|
|
3461
|
+
const d = dx * dx + dy * dy + dz * dz;
|
|
3462
|
+
if (d < bestDist) {
|
|
3463
|
+
bestDist = d;
|
|
3464
|
+
best = i;
|
|
3465
|
+
}
|
|
3639
3466
|
}
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
placeProgrammatic() {
|
|
3644
|
-
return this.driveInstant("place");
|
|
3467
|
+
this.grabbedIndex = best;
|
|
3468
|
+
this.wake();
|
|
3469
|
+
return best;
|
|
3645
3470
|
}
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3471
|
+
moveGrab(x, y, z26) {
|
|
3472
|
+
if (this.grabbedIndex < 0) return;
|
|
3473
|
+
const i3 = this.grabbedIndex * 3;
|
|
3474
|
+
this.positions[i3] = x;
|
|
3475
|
+
this.positions[i3 + 1] = y;
|
|
3476
|
+
this.positions[i3 + 2] = z26;
|
|
3477
|
+
this.prev[i3] = x;
|
|
3478
|
+
this.prev[i3 + 1] = y;
|
|
3479
|
+
this.prev[i3 + 2] = z26;
|
|
3480
|
+
this.wake();
|
|
3649
3481
|
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
if (!next || next === this.state) return false;
|
|
3653
|
-
this.goto(next, { instant: true });
|
|
3654
|
-
return true;
|
|
3482
|
+
release() {
|
|
3483
|
+
this.grabbedIndex = -1;
|
|
3655
3484
|
}
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
this.structure = clone(target);
|
|
3663
|
-
this.live = clone(target);
|
|
3664
|
-
const changed = {};
|
|
3665
|
-
for (const path in this.flat) {
|
|
3666
|
-
if (!(path in targetFlat)) delete this.flat[path];
|
|
3667
|
-
}
|
|
3668
|
-
for (const [path, value] of Object.entries(targetFlat)) {
|
|
3669
|
-
const current = this.flat[path];
|
|
3670
|
-
if (current === void 0) this.flat[path] = value;
|
|
3671
|
-
else if (current !== value) changed[path] = value;
|
|
3485
|
+
step(delta) {
|
|
3486
|
+
if (this.asleep) return;
|
|
3487
|
+
this.accumulator = Math.min(this.accumulator + delta, FIXED_DT * 4);
|
|
3488
|
+
while (this.accumulator >= FIXED_DT) {
|
|
3489
|
+
this.substep(FIXED_DT);
|
|
3490
|
+
this.accumulator -= FIXED_DT;
|
|
3672
3491
|
}
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
this.
|
|
3676
|
-
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3492
|
+
}
|
|
3493
|
+
substep(dt) {
|
|
3494
|
+
const { gravity, wind, stiffness, floor } = this.params;
|
|
3495
|
+
const p = this.positions;
|
|
3496
|
+
const damping = 0.985;
|
|
3497
|
+
const dt2 = dt * dt;
|
|
3498
|
+
this.time += dt;
|
|
3499
|
+
let maxTravel = 0;
|
|
3500
|
+
for (let i = 0; i < this.count; i++) {
|
|
3501
|
+
const i3 = i * 3;
|
|
3502
|
+
if (this.pinned[i] || i === this.grabbedIndex) {
|
|
3503
|
+
if (this.pinned[i]) {
|
|
3504
|
+
p[i3] = this.pinTargets[i3];
|
|
3505
|
+
p[i3 + 1] = this.pinTargets[i3 + 1];
|
|
3506
|
+
p[i3 + 2] = this.pinTargets[i3 + 2];
|
|
3507
|
+
}
|
|
3508
|
+
this.prev[i3] = p[i3];
|
|
3509
|
+
this.prev[i3 + 1] = p[i3 + 1];
|
|
3510
|
+
this.prev[i3 + 2] = p[i3 + 2];
|
|
3511
|
+
continue;
|
|
3682
3512
|
}
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3513
|
+
const x = p[i3];
|
|
3514
|
+
const y = p[i3 + 1];
|
|
3515
|
+
const z26 = p[i3 + 2];
|
|
3516
|
+
const gust2 = wind * (0.55 + 0.45 * Math.sin(this.time * 1.7 + x * 2.1 + y * 1.3)) * 0.9;
|
|
3517
|
+
const ax = gust2 * 0.25;
|
|
3518
|
+
const az = gust2;
|
|
3519
|
+
const vx = (x - this.prev[i3]) * damping;
|
|
3520
|
+
const vy = (y - this.prev[i3 + 1]) * damping;
|
|
3521
|
+
const vz = (z26 - this.prev[i3 + 2]) * damping;
|
|
3522
|
+
this.prev[i3] = x;
|
|
3523
|
+
this.prev[i3 + 1] = y;
|
|
3524
|
+
this.prev[i3 + 2] = z26;
|
|
3525
|
+
p[i3] = x + vx + ax * dt2;
|
|
3526
|
+
p[i3 + 1] = y + vy - gravity * 3.2 * dt2;
|
|
3527
|
+
p[i3 + 2] = z26 + vz + az * dt2;
|
|
3528
|
+
maxTravel = Math.max(maxTravel, vx * vx + vy * vy + vz * vz);
|
|
3687
3529
|
}
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3530
|
+
for (let iter = 0; iter < SOLVER_ITERATIONS; iter++) {
|
|
3531
|
+
for (const c of this.constraints) {
|
|
3532
|
+
const k = c.kind === 2 ? 0.25 + stiffness * 0.7 : c.kind === 1 ? 0.85 : 1;
|
|
3533
|
+
const a3 = c.a * 3;
|
|
3534
|
+
const b3 = c.b * 3;
|
|
3535
|
+
const dx = p[b3] - p[a3];
|
|
3536
|
+
const dy = p[b3 + 1] - p[a3 + 1];
|
|
3537
|
+
const dz = p[b3 + 2] - p[a3 + 2];
|
|
3538
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
3539
|
+
if (dist === 0) continue;
|
|
3540
|
+
const diff = (dist - c.rest) / dist * 0.5 * k;
|
|
3541
|
+
const aPinned = this.pinned[c.a] || c.a === this.grabbedIndex;
|
|
3542
|
+
const bPinned = this.pinned[c.b] || c.b === this.grabbedIndex;
|
|
3543
|
+
if (aPinned && bPinned) continue;
|
|
3544
|
+
const aw = aPinned ? 0 : bPinned ? 2 : 1;
|
|
3545
|
+
const bw = bPinned ? 0 : aPinned ? 2 : 1;
|
|
3546
|
+
p[a3] = p[a3] + dx * diff * aw;
|
|
3547
|
+
p[a3 + 1] = p[a3 + 1] + dy * diff * aw;
|
|
3548
|
+
p[a3 + 2] = p[a3 + 2] + dz * diff * aw;
|
|
3549
|
+
p[b3] = p[b3] - dx * diff * bw;
|
|
3550
|
+
p[b3 + 1] = p[b3 + 1] - dy * diff * bw;
|
|
3551
|
+
p[b3 + 2] = p[b3 + 2] - dz * diff * bw;
|
|
3696
3552
|
}
|
|
3697
|
-
});
|
|
3698
|
-
}
|
|
3699
|
-
/**
|
|
3700
|
-
* Swap the base config without resetting the machine — parameter edits and
|
|
3701
|
-
* runtime patches (torn perforation on detach) keep the current state and
|
|
3702
|
-
* live values instead of snapping back to `initial`. An in-flight tween is
|
|
3703
|
-
* left running on the SAME `flat` object, so the transition continues
|
|
3704
|
-
* smoothly to its target across the rebase (no freeze, no snap).
|
|
3705
|
-
*/
|
|
3706
|
-
rebase(base) {
|
|
3707
|
-
this.base = base;
|
|
3708
|
-
this.resolved.clear();
|
|
3709
|
-
const target = this.resolve(this.state);
|
|
3710
|
-
this.structure = clone(target);
|
|
3711
|
-
this.live = clone(target);
|
|
3712
|
-
const targetFlat = flattenNumeric(target);
|
|
3713
|
-
for (const path in this.flat) {
|
|
3714
|
-
if (!(path in targetFlat)) delete this.flat[path];
|
|
3715
3553
|
}
|
|
3716
|
-
for (
|
|
3717
|
-
|
|
3554
|
+
for (let i = 0; i < this.count; i++) {
|
|
3555
|
+
const i3 = i * 3;
|
|
3556
|
+
if (p[i3 + 1] < floor) {
|
|
3557
|
+
p[i3 + 1] = floor;
|
|
3558
|
+
this.prev[i3] = this.prev[i3] + (p[i3] - this.prev[i3]) * 0.5;
|
|
3559
|
+
this.prev[i3 + 2] = this.prev[i3 + 2] + (p[i3 + 2] - this.prev[i3 + 2]) * 0.5;
|
|
3560
|
+
}
|
|
3718
3561
|
}
|
|
3719
|
-
this.
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
resolve(state) {
|
|
3726
|
-
let config = this.resolved.get(state);
|
|
3727
|
-
if (!config) {
|
|
3728
|
-
config = resolveStateConfig(this.base, state);
|
|
3729
|
-
this.resolved.set(state, config);
|
|
3562
|
+
if (wind === 0 && this.grabbedIndex < 0) {
|
|
3563
|
+
if (maxTravel < SLEEP_EPSILON) {
|
|
3564
|
+
if (++this.stillFrames > SLEEP_FRAMES) this.asleep = true;
|
|
3565
|
+
} else {
|
|
3566
|
+
this.stillFrames = 0;
|
|
3567
|
+
}
|
|
3730
3568
|
}
|
|
3731
|
-
return config;
|
|
3732
|
-
}
|
|
3733
|
-
emitStructure() {
|
|
3734
|
-
this.opts.onChange?.(this.structuralConfig(), this.state);
|
|
3735
3569
|
}
|
|
3736
3570
|
};
|
|
3737
3571
|
|
|
3738
|
-
// src/
|
|
3739
|
-
import
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
}
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
}
|
|
3769
|
-
setAnimated({ config: c, state });
|
|
3770
|
-
},
|
|
3771
|
-
onAction: (event, state) => onActionRef.current?.(event, state)
|
|
3772
|
-
});
|
|
3773
|
-
machineRef.current = machine;
|
|
3774
|
-
lastStateRef.current = machine.state;
|
|
3775
|
-
setAnimated({ config: machine.structuralConfig(), state: machine.state });
|
|
3776
|
-
}, [key, live, instant]);
|
|
3777
|
-
useEffect3(
|
|
3778
|
-
() => () => {
|
|
3779
|
-
machineRef.current?.dispose();
|
|
3780
|
-
machineRef.current = null;
|
|
3781
|
-
},
|
|
3782
|
-
[]
|
|
3572
|
+
// src/surface/PaperMaterial.tsx
|
|
3573
|
+
import * as THREE6 from "three";
|
|
3574
|
+
import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
|
|
3575
|
+
import CustomShaderMaterial from "three-custom-shader-material";
|
|
3576
|
+
|
|
3577
|
+
// src/surface/compose.ts
|
|
3578
|
+
import * as THREE5 from "three";
|
|
3579
|
+
|
|
3580
|
+
// src/surface/translucency.ts
|
|
3581
|
+
import * as THREE4 from "three";
|
|
3582
|
+
var TRANSLUCENCY_VARYINGS = (
|
|
3583
|
+
/* glsl */
|
|
3584
|
+
`
|
|
3585
|
+
varying vec3 vPlWorldNormal;
|
|
3586
|
+
varying vec3 vPlViewDir;
|
|
3587
|
+
`
|
|
3588
|
+
);
|
|
3589
|
+
function translucencyVertexChunk(slots) {
|
|
3590
|
+
return (
|
|
3591
|
+
/* glsl */
|
|
3592
|
+
`
|
|
3593
|
+
{
|
|
3594
|
+
mat4 plModel = ${slots.model};
|
|
3595
|
+
vec4 plWorld = plModel * vec4(${slots.position}, 1.0);
|
|
3596
|
+
// Uniform scale only \u2014 layouts scale sheets evenly, so the plain 3\xD73 is
|
|
3597
|
+
// the correct normal matrix here and skips an inverse-transpose.
|
|
3598
|
+
vPlWorldNormal = normalize(mat3(plModel) * ${slots.normal});
|
|
3599
|
+
vPlViewDir = cameraPosition - plWorld.xyz;
|
|
3600
|
+
}
|
|
3601
|
+
`
|
|
3783
3602
|
);
|
|
3603
|
+
}
|
|
3604
|
+
var TRANSMISSION_GAIN = 0.5;
|
|
3605
|
+
var TRANSLUCENCY_FRAGMENT = (
|
|
3606
|
+
/* glsl */
|
|
3607
|
+
`
|
|
3608
|
+
uniform float uTranslucency;
|
|
3609
|
+
uniform vec3 uBackLightDir;
|
|
3610
|
+
uniform vec3 uBackLightColor;
|
|
3611
|
+
uniform float uAmbientTransmission;
|
|
3612
|
+
${TRANSLUCENCY_VARYINGS}
|
|
3613
|
+
|
|
3614
|
+
vec3 plTransmission(vec3 inkFilter) {
|
|
3615
|
+
if (uTranslucency <= 0.0) return vec3(0.0);
|
|
3616
|
+
vec3 n = normalize(vPlWorldNormal);
|
|
3617
|
+
// Sheets render double-sided; the back face needs the normal it actually shows.
|
|
3618
|
+
if (!gl_FrontFacing) n = -n;
|
|
3619
|
+
// The lamp is BEHIND this sheet when the face we are looking at points away
|
|
3620
|
+
// from it \u2014 that is the whole test.
|
|
3621
|
+
float behind = clamp(-dot(n, uBackLightDir), 0.0, 1.0);
|
|
3622
|
+
// A grazing view looks through more paper, and more paper passes less light.
|
|
3623
|
+
float thickness = abs(dot(n, normalize(vPlViewDir)));
|
|
3624
|
+
// Paper in a lit room glows whatever way it is turned \u2014 a sheet standing
|
|
3625
|
+
// edge-on to the only lamp is not black. Without this floor, a banner
|
|
3626
|
+
// whose face runs parallel to the key light gets neither diffuse nor
|
|
3627
|
+
// transmission and drops out of the picture entirely.
|
|
3628
|
+
vec3 arriving = uBackLightColor * behind + uAmbientTransmission;
|
|
3629
|
+
return arriving * uTranslucency * mix(0.25, 1.0, thickness) * inkFilter;
|
|
3630
|
+
}
|
|
3631
|
+
`
|
|
3632
|
+
);
|
|
3633
|
+
function translucencyValues(translucency, lighting) {
|
|
3634
|
+
const preset = typeof lighting === "string" ? getLightingPreset(lighting) : lighting;
|
|
3635
|
+
const [x, y, z26] = preset.key.position;
|
|
3636
|
+
const direction = new THREE4.Vector3(x, y, z26);
|
|
3637
|
+
if (direction.lengthSq() < 1e-12) direction.set(0, 1, 0);
|
|
3638
|
+
direction.normalize();
|
|
3639
|
+
const color = new THREE4.Color(preset.key.color).multiplyScalar(preset.key.intensity * TRANSMISSION_GAIN);
|
|
3640
|
+
return { translucency, direction, color, ambient: preset.ambient * TRANSMISSION_GAIN };
|
|
3641
|
+
}
|
|
3642
|
+
function translucencyUniforms(translucency, lighting) {
|
|
3643
|
+
const values = translucencyValues(translucency, lighting);
|
|
3784
3644
|
return {
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3645
|
+
uTranslucency: { value: values.translucency },
|
|
3646
|
+
uBackLightDir: { value: values.direction },
|
|
3647
|
+
uBackLightColor: { value: values.color },
|
|
3648
|
+
uAmbientTransmission: { value: values.ambient }
|
|
3788
3649
|
};
|
|
3789
3650
|
}
|
|
3790
3651
|
|
|
3791
|
-
// src/
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3652
|
+
// src/surface/compose.ts
|
|
3653
|
+
var VERTEX = (
|
|
3654
|
+
/* glsl */
|
|
3655
|
+
`
|
|
3656
|
+
varying vec2 vPaperUv;
|
|
3657
|
+
${TRANSLUCENCY_VARYINGS}
|
|
3658
|
+
void main() {
|
|
3659
|
+
vPaperUv = uv;
|
|
3660
|
+
${translucencyVertexChunk({ model: "modelMatrix", position: "position", normal: "normal" })}
|
|
3661
|
+
}
|
|
3662
|
+
`
|
|
3663
|
+
);
|
|
3664
|
+
var HELPERS = (
|
|
3665
|
+
/* glsl */
|
|
3666
|
+
`
|
|
3667
|
+
varying vec2 vPaperUv;
|
|
3668
|
+
uniform float uBackDarken;
|
|
3796
3669
|
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
function useStable(value) {
|
|
3800
|
-
const held = useRef2(value);
|
|
3801
|
-
if (!deepEqual(held.current, value)) held.current = value;
|
|
3802
|
-
return held.current;
|
|
3670
|
+
float plHash(vec2 p) {
|
|
3671
|
+
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
|
|
3803
3672
|
}
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3673
|
+
|
|
3674
|
+
float plNoise(vec2 p) {
|
|
3675
|
+
vec2 i = floor(p);
|
|
3676
|
+
vec2 f = fract(p);
|
|
3677
|
+
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
3678
|
+
return mix(
|
|
3679
|
+
mix(plHash(i), plHash(i + vec2(1.0, 0.0)), u.x),
|
|
3680
|
+
mix(plHash(i + vec2(0.0, 1.0)), plHash(i + vec2(1.0, 1.0)), u.x),
|
|
3681
|
+
u.y
|
|
3682
|
+
);
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
float plFbm(vec2 p) {
|
|
3686
|
+
float v = 0.0;
|
|
3687
|
+
float a = 0.5;
|
|
3688
|
+
for (int i = 0; i < 4; i++) {
|
|
3689
|
+
v += a * plNoise(p);
|
|
3690
|
+
p *= 2.03;
|
|
3691
|
+
a *= 0.5;
|
|
3811
3692
|
}
|
|
3812
|
-
|
|
3813
|
-
const right = b;
|
|
3814
|
-
const keys = /* @__PURE__ */ new Set();
|
|
3815
|
-
for (const key of Object.keys(left)) if (left[key] !== void 0) keys.add(key);
|
|
3816
|
-
for (const key of Object.keys(right)) if (right[key] !== void 0) keys.add(key);
|
|
3817
|
-
for (const key of keys) if (!deepEqual(left[key], right[key])) return false;
|
|
3818
|
-
return true;
|
|
3693
|
+
return v;
|
|
3819
3694
|
}
|
|
3695
|
+
`
|
|
3696
|
+
);
|
|
3697
|
+
var edgeFlags = (edges) => new THREE5.Vector4(
|
|
3698
|
+
edges.includes("top") ? 1 : 0,
|
|
3699
|
+
edges.includes("right") ? 1 : 0,
|
|
3700
|
+
edges.includes("bottom") ? 1 : 0,
|
|
3701
|
+
edges.includes("left") ? 1 : 0
|
|
3702
|
+
);
|
|
3703
|
+
var GRAIN_CHUNK = (
|
|
3704
|
+
/* glsl */
|
|
3705
|
+
`
|
|
3706
|
+
uniform float uGrainAmount;
|
|
3707
|
+
uniform float uGrainBanding;
|
|
3820
3708
|
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3709
|
+
void plGrain(inout vec4 color, inout float rough) {
|
|
3710
|
+
float fiber = plFbm(vPaperUv * 240.0);
|
|
3711
|
+
float fleck = plNoise(vPaperUv * 900.0);
|
|
3712
|
+
float g = mix(0.5, fiber * 0.75 + fleck * 0.25, uGrainAmount);
|
|
3713
|
+
color.rgb *= 0.92 + g * 0.16;
|
|
3714
|
+
rough = clamp(rough + (g - 0.5) * uGrainAmount * 0.35, 0.0, 1.0);
|
|
3715
|
+
// Thermal-printer banding: faint horizontal density stripes.
|
|
3716
|
+
if (uGrainBanding > 0.0) {
|
|
3717
|
+
float band = sin(vPaperUv.y * 700.0) * 0.5 + 0.5;
|
|
3718
|
+
color.rgb *= 1.0 - uGrainBanding * 0.05 * band;
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
`
|
|
3722
|
+
);
|
|
3723
|
+
var DECKLE_CHUNK = (
|
|
3724
|
+
/* glsl */
|
|
3725
|
+
`
|
|
3726
|
+
uniform vec4 uDeckleEdges; // top, right, bottom, left
|
|
3727
|
+
uniform float uDeckleRoughness;
|
|
3824
3728
|
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
const bodyStep = size * 1.35;
|
|
3841
|
-
const bodyLines = content.body ? wrapLines(ctx, content.body, maxWidth, `${size}px ${content.font}`) : [];
|
|
3842
|
-
const titleBlock = content.title ? titleSize * 1.9 : 0;
|
|
3843
|
-
const ruleBlock = content.rule && content.title ? titleSize * 0.9 : 0;
|
|
3844
|
-
const noteBlock = content.note ? noteSize * 2.4 : 0;
|
|
3845
|
-
const bodyBlock = bodyLines.length * bodyStep;
|
|
3846
|
-
const total = titleBlock + ruleBlock + bodyBlock + noteBlock;
|
|
3847
|
-
let y = Math.max(pad, (h - total) / 2) + size * 0.9;
|
|
3848
|
-
if (content.title) {
|
|
3849
|
-
ctx.font = `${titleSize}px ${content.font}`;
|
|
3850
|
-
ctx.letterSpacing = `${TITLE_TRACKING}em`;
|
|
3851
|
-
ctx.fillStyle = ink;
|
|
3852
|
-
ctx.globalAlpha = 0.72;
|
|
3853
|
-
ctx.fillText(content.title.toUpperCase(), x, y - size * 0.5);
|
|
3854
|
-
ctx.globalAlpha = 1;
|
|
3855
|
-
ctx.letterSpacing = "0em";
|
|
3856
|
-
y += titleBlock - size * 0.5;
|
|
3857
|
-
if (content.rule) {
|
|
3858
|
-
ctx.save();
|
|
3859
|
-
ctx.strokeStyle = ink;
|
|
3860
|
-
ctx.globalAlpha = 0.28;
|
|
3861
|
-
ctx.lineWidth = Math.max(1, dpr * 0.75);
|
|
3862
|
-
ctx.beginPath();
|
|
3863
|
-
ctx.moveTo(content.align === "center" ? w / 2 - maxWidth / 2 : pad, y - titleSize * 0.5);
|
|
3864
|
-
ctx.lineTo(content.align === "center" ? w / 2 + maxWidth / 2 : pad + maxWidth, y - titleSize * 0.5);
|
|
3865
|
-
ctx.stroke();
|
|
3866
|
-
ctx.restore();
|
|
3867
|
-
y += ruleBlock;
|
|
3868
|
-
}
|
|
3729
|
+
void plDeckle(inout vec4 color) {
|
|
3730
|
+
// Distance to each selected edge, gnawed by low-frequency noise.
|
|
3731
|
+
float depth = 0.012 + uDeckleRoughness * 0.05;
|
|
3732
|
+
float tear = 1.0;
|
|
3733
|
+
float fiberBand = 0.0;
|
|
3734
|
+
vec4 dists = vec4(1.0 - vPaperUv.y, 1.0 - vPaperUv.x, vPaperUv.y, vPaperUv.x);
|
|
3735
|
+
vec4 alongs = vec4(vPaperUv.x, vPaperUv.y, vPaperUv.x, vPaperUv.y);
|
|
3736
|
+
for (int e = 0; e < 4; e++) {
|
|
3737
|
+
if (uDeckleEdges[e] < 0.5) continue;
|
|
3738
|
+
float n = plFbm(vec2(alongs[e] * 26.0, float(e) * 7.31)) - 0.5;
|
|
3739
|
+
float boundary = depth * (0.55 + n * 1.6);
|
|
3740
|
+
float d = dists[e] - boundary;
|
|
3741
|
+
tear = min(tear, step(0.0, d));
|
|
3742
|
+
// Lightened fiber band just inside the tear.
|
|
3743
|
+
fiberBand = max(fiberBand, smoothstep(depth * 1.4, 0.0, d) * step(0.0, d));
|
|
3869
3744
|
}
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3745
|
+
color.a *= tear;
|
|
3746
|
+
color.rgb = mix(color.rgb, vec3(1.0), fiberBand * 0.35);
|
|
3747
|
+
}
|
|
3748
|
+
`
|
|
3749
|
+
);
|
|
3750
|
+
var CREASE_CHUNK = (
|
|
3751
|
+
/* glsl */
|
|
3752
|
+
`
|
|
3753
|
+
uniform float uCreaseAngle;
|
|
3754
|
+
uniform float uCreaseStrength;
|
|
3755
|
+
uniform float uCreasePositions[4];
|
|
3756
|
+
uniform int uCreaseCount;
|
|
3757
|
+
|
|
3758
|
+
void plCrease(inout vec4 color, inout float rough) {
|
|
3759
|
+
vec2 dir = vec2(cos(uCreaseAngle), sin(uCreaseAngle));
|
|
3760
|
+
// Coordinate across the crease lines (0..1 over the sheet).
|
|
3761
|
+
float t = dot(vPaperUv - 0.5, vec2(-dir.y, dir.x)) + 0.5;
|
|
3762
|
+
for (int i = 0; i < 4; i++) {
|
|
3763
|
+
if (i >= uCreaseCount) break;
|
|
3764
|
+
float d = abs(t - uCreasePositions[i]);
|
|
3765
|
+
float shadow = smoothstep(0.014, 0.0, d);
|
|
3766
|
+
float sheen = smoothstep(0.02, 0.006, d) - smoothstep(0.006, 0.0, d);
|
|
3767
|
+
color.rgb *= 1.0 - shadow * uCreaseStrength * 0.28;
|
|
3768
|
+
color.rgb += sheen * uCreaseStrength * 0.05;
|
|
3769
|
+
rough = clamp(rough + shadow * uCreaseStrength * 0.2, 0.0, 1.0);
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
`
|
|
3773
|
+
);
|
|
3774
|
+
var PERFORATION_CHUNK = (
|
|
3775
|
+
/* glsl */
|
|
3776
|
+
`
|
|
3777
|
+
uniform vec4 uPerfEdges; // top, right, bottom, left enabled
|
|
3778
|
+
uniform vec4 uPerfTorn; // 1 = ripped-through profile, 0 = clean punches
|
|
3779
|
+
uniform float uPerfRadius; // world units
|
|
3780
|
+
uniform float uPerfSpacing;
|
|
3781
|
+
uniform vec2 uSheetSize;
|
|
3782
|
+
|
|
3783
|
+
void plPerforation(inout vec4 color) {
|
|
3784
|
+
// Per-edge distance/along coordinates, converted from UV to world units so
|
|
3785
|
+
// hole size is stable across sheet dimensions.
|
|
3786
|
+
vec4 dists = vec4(1.0 - vPaperUv.y, 1.0 - vPaperUv.x, vPaperUv.y, vPaperUv.x);
|
|
3787
|
+
vec4 alongs = vec4(vPaperUv.x, vPaperUv.y, vPaperUv.x, vPaperUv.y);
|
|
3788
|
+
vec4 distScale = vec4(uSheetSize.y, uSheetSize.x, uSheetSize.y, uSheetSize.x);
|
|
3789
|
+
vec4 alongScale = vec4(uSheetSize.x, uSheetSize.y, uSheetSize.x, uSheetSize.y);
|
|
3790
|
+
float fiber = 0.0;
|
|
3791
|
+
for (int e = 0; e < 4; e++) {
|
|
3792
|
+
if (uPerfEdges[e] < 0.5) continue;
|
|
3793
|
+
float d = dists[e] * distScale[e];
|
|
3794
|
+
float a = alongs[e] * alongScale[e];
|
|
3795
|
+
// Signed distance along the edge to the nearest hole center.
|
|
3796
|
+
float cell = mod(a + uPerfSpacing * 0.5, uPerfSpacing) - uPerfSpacing * 0.5;
|
|
3797
|
+
if (uPerfTorn[e] < 0.5) {
|
|
3798
|
+
// Intact: clean semicircular punches on the edge line (alphaTest, not
|
|
3799
|
+
// blending \u2014 shadow correctness).
|
|
3800
|
+
if (length(vec2(cell, d)) < uPerfRadius) color.a = 0.0;
|
|
3801
|
+
} else {
|
|
3802
|
+
// Torn: ripped profile following the hole rhythm \u2014 alternating tabs and
|
|
3803
|
+
// notches, gnawed by noise, with a lightened fiber band along the tear.
|
|
3804
|
+
float rhythm = abs(sin(a / uPerfSpacing * 3.14159265));
|
|
3805
|
+
float n = plNoise(vec2(a * 40.0, float(e) * 7.31)) - 0.5;
|
|
3806
|
+
float cut = uPerfRadius * (0.35 + rhythm * 1.35 + n * 0.9);
|
|
3807
|
+
if (d < cut) color.a = 0.0;
|
|
3808
|
+
fiber = max(fiber, smoothstep(uPerfRadius * 2.4, 0.0, d - cut) * step(cut, d));
|
|
3881
3809
|
}
|
|
3882
|
-
ctx.restore();
|
|
3883
|
-
}
|
|
3884
|
-
ctx.font = `${size}px ${content.font}`;
|
|
3885
|
-
ctx.fillStyle = ink;
|
|
3886
|
-
for (const line of bodyLines) {
|
|
3887
|
-
if (y > h - pad) break;
|
|
3888
|
-
ctx.fillText(line, x, y);
|
|
3889
|
-
y += bodyStep;
|
|
3890
|
-
}
|
|
3891
|
-
if (content.note) {
|
|
3892
|
-
ctx.font = `${noteSize}px ${content.font}`;
|
|
3893
|
-
ctx.letterSpacing = `${NOTE_TRACKING}em`;
|
|
3894
|
-
ctx.globalAlpha = 0.6;
|
|
3895
|
-
ctx.fillText(content.note, x, Math.min(y + noteSize * 1.4, h - pad));
|
|
3896
|
-
ctx.globalAlpha = 1;
|
|
3897
|
-
ctx.letterSpacing = "0em";
|
|
3898
3810
|
}
|
|
3811
|
+
color.rgb = mix(color.rgb, vec3(1.0), fiber * 0.4);
|
|
3899
3812
|
}
|
|
3813
|
+
`
|
|
3814
|
+
);
|
|
3815
|
+
var AGING_CHUNK = (
|
|
3816
|
+
/* glsl */
|
|
3817
|
+
`
|
|
3818
|
+
uniform float uAgingAmount;
|
|
3900
3819
|
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
function paintBackground(ctx, w, h, stock) {
|
|
3911
|
-
ctx.fillStyle = stock.color;
|
|
3912
|
-
ctx.fillRect(0, 0, w, h);
|
|
3913
|
-
}
|
|
3914
|
-
function paintImage(ctx, w, h, img, fit) {
|
|
3915
|
-
const scale = fit === "cover" ? Math.max(w / img.width, h / img.height) : Math.min(w / img.width, h / img.height);
|
|
3916
|
-
const dw = img.width * scale;
|
|
3917
|
-
const dh = img.height * scale;
|
|
3918
|
-
ctx.drawImage(img, (w - dw) / 2, (h - dh) / 2, dw, dh);
|
|
3820
|
+
void plAging(inout vec4 color) {
|
|
3821
|
+
// Yellowing deepens toward the edges, like light exposure.
|
|
3822
|
+
float edge = max(abs(vPaperUv.x - 0.5), abs(vPaperUv.y - 0.5)) * 2.0;
|
|
3823
|
+
vec3 yellowed = color.rgb * vec3(1.0, 0.94, 0.78);
|
|
3824
|
+
color.rgb = mix(color.rgb, yellowed, uAgingAmount * (0.45 + edge * 0.55));
|
|
3825
|
+
// Foxing: sparse rusty blotches.
|
|
3826
|
+
float fox = plFbm(vPaperUv * 14.0 + 3.7);
|
|
3827
|
+
float spots = smoothstep(0.62, 0.78, fox) * uAgingAmount;
|
|
3828
|
+
color.rgb = mix(color.rgb, vec3(0.62, 0.45, 0.26), spots * 0.5);
|
|
3919
3829
|
}
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
const
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
const
|
|
3930
|
-
const
|
|
3931
|
-
const
|
|
3932
|
-
const
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3830
|
+
`
|
|
3831
|
+
);
|
|
3832
|
+
function composeSurface(surface, stock, thickness, maps = { hasFrontMap: false, hasBackMap: false }, sheet2 = { width: 1, height: 1.4 }, lighting = "studio") {
|
|
3833
|
+
const grain = surface.grain ?? stock.defaultSurface.grain;
|
|
3834
|
+
const aging = surface.aging ?? stock.defaultSurface.aging;
|
|
3835
|
+
const deckle = surface.deckle;
|
|
3836
|
+
const creases = surface.creaseLines;
|
|
3837
|
+
const perforation = surface.perforation;
|
|
3838
|
+
const banding = stock.banding;
|
|
3839
|
+
const showThrough = stock.adhesive ? 0 : surface.showThrough ?? stock.showThrough;
|
|
3840
|
+
const chunks = [];
|
|
3841
|
+
const calls = [];
|
|
3842
|
+
const uniforms = {
|
|
3843
|
+
// Backside darkening: thicker/opaque stock lets less light through.
|
|
3844
|
+
// Adhesive backs skip it — the glue layer is its own bright surface.
|
|
3845
|
+
uBackDarken: {
|
|
3846
|
+
value: stock.adhesive ? 1 : 1 - Math.min(0.45, 0.12 + thickness * 0.9) * stock.opacity
|
|
3847
|
+
},
|
|
3848
|
+
uStockColor: { value: new THREE5.Color(stock.color) },
|
|
3849
|
+
uOpacity: { value: stock.opacity },
|
|
3850
|
+
uShowThrough: { value: showThrough },
|
|
3851
|
+
// Always compiled in: the shader early-outs at zero translucency, which
|
|
3852
|
+
// is cheaper than carrying a second program structure for it.
|
|
3853
|
+
...translucencyUniforms(surface.translucency ?? stock.translucency, lighting)
|
|
3854
|
+
};
|
|
3855
|
+
if (maps.hasFrontMap) uniforms.uFrontMap = { value: null };
|
|
3856
|
+
if (maps.hasBackMap) uniforms.uBackMap = { value: null };
|
|
3857
|
+
if (grain !== void 0 || banding > 0) {
|
|
3858
|
+
chunks.push(GRAIN_CHUNK);
|
|
3859
|
+
calls.push("plGrain(csm_DiffuseColor, csm_Roughness);");
|
|
3860
|
+
uniforms.uGrainAmount = { value: grain ?? 0 };
|
|
3861
|
+
uniforms.uGrainBanding = { value: banding };
|
|
3941
3862
|
}
|
|
3942
|
-
|
|
3863
|
+
if (deckle) {
|
|
3864
|
+
chunks.push(DECKLE_CHUNK);
|
|
3865
|
+
calls.push("plDeckle(csm_DiffuseColor);");
|
|
3866
|
+
uniforms.uDeckleEdges = { value: edgeFlags(deckle.edges) };
|
|
3867
|
+
uniforms.uDeckleRoughness = { value: deckle.roughness };
|
|
3868
|
+
}
|
|
3869
|
+
if (perforation) {
|
|
3870
|
+
const edges = perforation.edges === "all" ? [...paperEdges] : perforation.edges;
|
|
3871
|
+
chunks.push(PERFORATION_CHUNK);
|
|
3872
|
+
calls.push("plPerforation(csm_DiffuseColor);");
|
|
3873
|
+
uniforms.uPerfEdges = { value: edgeFlags(edges) };
|
|
3874
|
+
uniforms.uPerfTorn = {
|
|
3875
|
+
value: new THREE5.Vector4(
|
|
3876
|
+
...paperEdges.map((e) => edges.includes(e) && perforation.state[e] === "torn" ? 1 : 0)
|
|
3877
|
+
)
|
|
3878
|
+
};
|
|
3879
|
+
uniforms.uPerfRadius = { value: perforation.holeRadius };
|
|
3880
|
+
uniforms.uPerfSpacing = { value: perforation.spacing };
|
|
3881
|
+
uniforms.uSheetSize = { value: new THREE5.Vector2(sheet2.width, sheet2.height) };
|
|
3882
|
+
}
|
|
3883
|
+
if (creases) {
|
|
3884
|
+
chunks.push(CREASE_CHUNK);
|
|
3885
|
+
calls.push("plCrease(csm_DiffuseColor, csm_Roughness);");
|
|
3886
|
+
uniforms.uCreaseAngle = { value: creases.angle * Math.PI / 180 };
|
|
3887
|
+
uniforms.uCreaseStrength = { value: creases.strength };
|
|
3888
|
+
uniforms.uCreasePositions = { value: padPositions(creases.positions) };
|
|
3889
|
+
uniforms.uCreaseCount = { value: Math.min(creases.positions.length, 4) };
|
|
3890
|
+
}
|
|
3891
|
+
if (aging !== void 0) {
|
|
3892
|
+
chunks.push(AGING_CHUNK);
|
|
3893
|
+
calls.push("plAging(csm_DiffuseColor);");
|
|
3894
|
+
uniforms.uAgingAmount = { value: aging };
|
|
3895
|
+
}
|
|
3896
|
+
const frontExpr = maps.hasFrontMap ? "texture2D(uFrontMap, vPaperUv).rgb" : "uStockColor";
|
|
3897
|
+
const backBaseExpr = stock.adhesive ? "vec3(0.965, 0.96, 0.945)" : maps.hasBackMap ? "texture2D(uBackMap, vec2(1.0 - vPaperUv.x, vPaperUv.y)).rgb" : "uStockColor";
|
|
3898
|
+
const fragmentShader = (
|
|
3899
|
+
/* glsl */
|
|
3900
|
+
`
|
|
3901
|
+
${HELPERS}
|
|
3902
|
+
uniform vec3 uStockColor;
|
|
3903
|
+
uniform float uOpacity;
|
|
3904
|
+
uniform float uShowThrough;
|
|
3905
|
+
${maps.hasFrontMap ? "uniform sampler2D uFrontMap;" : ""}
|
|
3906
|
+
${maps.hasBackMap && !stock.adhesive ? "uniform sampler2D uBackMap;" : ""}
|
|
3907
|
+
${TRANSLUCENCY_FRAGMENT}
|
|
3908
|
+
${chunks.join("\n")}
|
|
3909
|
+
void main() {
|
|
3910
|
+
vec3 front = ${frontExpr};
|
|
3911
|
+
if (gl_FrontFacing) {
|
|
3912
|
+
csm_DiffuseColor = vec4(front, uOpacity);
|
|
3913
|
+
} else {
|
|
3914
|
+
vec3 backBase = ${backBaseExpr};
|
|
3915
|
+
csm_DiffuseColor = vec4(backBase * mix(vec3(1.0), front, uShowThrough), uOpacity);
|
|
3916
|
+
}
|
|
3917
|
+
${calls.join("\n ")}
|
|
3918
|
+
if (!gl_FrontFacing) csm_DiffuseColor.rgb *= uBackDarken;
|
|
3919
|
+
${stock.adhesive ? "// Adhesive underside: higher specular than the printed face.\n if (!gl_FrontFacing) csm_Roughness = 0.18;" : ""}
|
|
3920
|
+
// What the key light pushes through the sheet, filtered by the ink on it.
|
|
3921
|
+
csm_Emissive = plTransmission(front);
|
|
3943
3922
|
}
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3923
|
+
`
|
|
3924
|
+
);
|
|
3925
|
+
return {
|
|
3926
|
+
structureKey: `${[
|
|
3927
|
+
grain !== void 0 || banding > 0 ? "g" : "",
|
|
3928
|
+
deckle ? "d" : "",
|
|
3929
|
+
creases ? "c" : "",
|
|
3930
|
+
aging !== void 0 ? "a" : "",
|
|
3931
|
+
perforation ? "p" : "",
|
|
3932
|
+
stock.adhesive ? "A" : ""
|
|
3933
|
+
].join("")}:${maps.hasFrontMap ? "F" : ""}${maps.hasBackMap ? "B" : ""}`,
|
|
3934
|
+
vertexShader: VERTEX,
|
|
3935
|
+
fragmentShader,
|
|
3936
|
+
uniforms,
|
|
3937
|
+
alphaTest: deckle || perforation ? 0.5 : 0
|
|
3938
|
+
};
|
|
3956
3939
|
}
|
|
3957
|
-
function
|
|
3958
|
-
const
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
tex.generateMipmaps = true;
|
|
3962
|
-
return tex;
|
|
3940
|
+
function padPositions(positions) {
|
|
3941
|
+
const out = positions.slice(0, 4);
|
|
3942
|
+
while (out.length < 4) out.push(-1);
|
|
3943
|
+
return out;
|
|
3963
3944
|
}
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3945
|
+
|
|
3946
|
+
// src/surface/PaperMaterial.tsx
|
|
3947
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
3948
|
+
function PaperMaterial({
|
|
3949
|
+
stock,
|
|
3950
|
+
texture,
|
|
3951
|
+
backTexture,
|
|
3952
|
+
surface,
|
|
3953
|
+
thickness,
|
|
3954
|
+
sheet: sheet2,
|
|
3955
|
+
lighting = "studio"
|
|
3956
|
+
}) {
|
|
3957
|
+
const rig = useLightRig(lighting);
|
|
3958
|
+
const composed = composeSurface(
|
|
3959
|
+
surface,
|
|
3960
|
+
stock,
|
|
3961
|
+
thickness,
|
|
3962
|
+
{
|
|
3963
|
+
hasFrontMap: Boolean(texture),
|
|
3964
|
+
hasBackMap: Boolean(backTexture)
|
|
3965
|
+
},
|
|
3966
|
+
sheet2,
|
|
3967
|
+
rig
|
|
3968
|
+
);
|
|
3969
|
+
const bound = useMemo2(() => composed.uniforms, [composed.structureKey]);
|
|
3967
3970
|
useEffect4(() => {
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
tex = makeTexture(canvas);
|
|
3977
|
-
setTexture(tex);
|
|
3978
|
-
};
|
|
3979
|
-
if (content.type === "image" && content.src) {
|
|
3980
|
-
const img = new Image();
|
|
3981
|
-
img.crossOrigin = "anonymous";
|
|
3982
|
-
img.onload = () => commit(renderContentToCanvas(content, sheet2, stock, img));
|
|
3983
|
-
img.onerror = () => commit(renderContentToCanvas(content, sheet2, stock));
|
|
3984
|
-
img.src = content.src;
|
|
3985
|
-
} else if (content.type === "text" || content.type === "card") {
|
|
3986
|
-
void ensureFont(content.font, content.size * DPR).then(
|
|
3987
|
-
() => commit(renderContentToCanvas(content, sheet2, stock))
|
|
3988
|
-
);
|
|
3989
|
-
} else if (content.type === "receipt") {
|
|
3990
|
-
document.fonts.ready.then(() => commit(renderContentToCanvas(content, sheet2, stock)));
|
|
3991
|
-
} else {
|
|
3992
|
-
commit(renderContentToCanvas(content, sheet2, stock));
|
|
3971
|
+
for (const [key, uniform] of Object.entries(composed.uniforms)) {
|
|
3972
|
+
if (!bound[key] || key === "uFrontMap" || key === "uBackMap") continue;
|
|
3973
|
+
if (bound[key].value instanceof THREE6.Color && uniform.value instanceof THREE6.Color) {
|
|
3974
|
+
;
|
|
3975
|
+
bound[key].value.copy(uniform.value);
|
|
3976
|
+
} else {
|
|
3977
|
+
bound[key].value = uniform.value;
|
|
3978
|
+
}
|
|
3993
3979
|
}
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
}, [
|
|
3999
|
-
return
|
|
3980
|
+
});
|
|
3981
|
+
useEffect4(() => {
|
|
3982
|
+
if (bound.uFrontMap) bound.uFrontMap.value = texture;
|
|
3983
|
+
if (bound.uBackMap) bound.uBackMap.value = backTexture ?? null;
|
|
3984
|
+
}, [bound, texture, backTexture]);
|
|
3985
|
+
return /* @__PURE__ */ jsx3(
|
|
3986
|
+
CustomShaderMaterial,
|
|
3987
|
+
{
|
|
3988
|
+
baseMaterial: THREE6.MeshStandardMaterial,
|
|
3989
|
+
vertexShader: composed.vertexShader,
|
|
3990
|
+
fragmentShader: composed.fragmentShader,
|
|
3991
|
+
uniforms: bound,
|
|
3992
|
+
color: "#ffffff",
|
|
3993
|
+
roughness: stock.roughness,
|
|
3994
|
+
metalness: 0,
|
|
3995
|
+
transparent: stock.opacity < 1,
|
|
3996
|
+
opacity: stock.opacity,
|
|
3997
|
+
alphaTest: composed.alphaTest,
|
|
3998
|
+
side: THREE6.DoubleSide
|
|
3999
|
+
},
|
|
4000
|
+
composed.structureKey
|
|
4001
|
+
);
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
// src/motion/onTwos.ts
|
|
4005
|
+
var ON_TWOS_FPS = 12;
|
|
4006
|
+
function quantizeTime(t, fps = ON_TWOS_FPS) {
|
|
4007
|
+
return Math.floor(t * fps) / fps;
|
|
4008
|
+
}
|
|
4009
|
+
function quantizeProgress(p, duration, fps = ON_TWOS_FPS) {
|
|
4010
|
+
const steps = Math.max(1, Math.round(duration * fps));
|
|
4011
|
+
return Math.round(p * steps) / steps;
|
|
4000
4012
|
}
|
|
4001
4013
|
|
|
4002
4014
|
// src/PaperMesh.tsx
|
|
@@ -4792,305 +4804,74 @@ function DropZoneVisual({ registry: registry4, config }) {
|
|
|
4792
4804
|
/* @__PURE__ */ jsx6("planeGeometry", { args: [w, h] }),
|
|
4793
4805
|
/* @__PURE__ */ jsx6(
|
|
4794
4806
|
"meshBasicMaterial",
|
|
4795
|
-
{
|
|
4796
|
-
color: hovered && style === "glow" ? "#8ea8ff" : "#5c6f9e",
|
|
4797
|
-
transparent: true,
|
|
4798
|
-
opacity: hovered && style === "glow" ? 0.32 : 0.14,
|
|
4799
|
-
depthWrite: false
|
|
4800
|
-
}
|
|
4801
|
-
)
|
|
4802
|
-
] }),
|
|
4803
|
-
/* @__PURE__ */ jsx6("lineSegments", { geometry: edges, children: /* @__PURE__ */ jsx6("lineBasicMaterial", { color: hovered ? "#aebfff" : "#6b7da8" }) })
|
|
4804
|
-
] });
|
|
4805
|
-
}
|
|
4806
|
-
|
|
4807
|
-
// src/field/sheetGrid.ts
|
|
4808
|
-
import { z as z23 } from "zod";
|
|
4809
|
-
var sheetLayoutSchema = z23.object({
|
|
4810
|
-
rows: z23.number().int().min(1).max(12).default(2),
|
|
4811
|
-
columns: z23.number().int().min(1).max(12).default(5),
|
|
4812
|
-
/** World-units gap between slots. Stamps are printed in register — no jitter. */
|
|
4813
|
-
gutter: z23.number().min(0).max(1).default(0.08),
|
|
4814
|
-
/** Slot footprint in world units (the paper preset should match). */
|
|
4815
|
-
cellWidth: z23.number().min(0.1).max(4).default(0.72),
|
|
4816
|
-
cellHeight: z23.number().min(0.1).max(4).default(0.86),
|
|
4817
|
-
/** Render the shared backing sheet behind the grid. */
|
|
4818
|
-
backing: z23.boolean().default(true),
|
|
4819
|
-
backingMargin: z23.number().min(0).max(1).default(0.12)
|
|
4820
|
-
});
|
|
4821
|
-
var SHEET_LIFT = 0.012;
|
|
4822
|
-
function withSheetCellFromPaper(parsed, rawOptions, paperDims) {
|
|
4823
|
-
if (!paperDims) return parsed;
|
|
4824
|
-
const hasW = rawOptions !== void 0 && rawOptions.cellWidth !== void 0;
|
|
4825
|
-
const hasH = rawOptions !== void 0 && rawOptions.cellHeight !== void 0;
|
|
4826
|
-
if (hasW && hasH) return parsed;
|
|
4827
|
-
return {
|
|
4828
|
-
...parsed,
|
|
4829
|
-
cellWidth: hasW ? parsed.cellWidth : paperDims.width,
|
|
4830
|
-
cellHeight: hasH ? parsed.cellHeight : paperDims.height
|
|
4831
|
-
};
|
|
4832
|
-
}
|
|
4833
|
-
function sheetSlotXY(i, o) {
|
|
4834
|
-
const col = i % o.columns;
|
|
4835
|
-
const row = Math.floor(i / o.columns);
|
|
4836
|
-
return {
|
|
4837
|
-
x: (col - (o.columns - 1) / 2) * (o.cellWidth + o.gutter),
|
|
4838
|
-
y: ((o.rows - 1) / 2 - row) * (o.cellHeight + o.gutter)
|
|
4839
|
-
};
|
|
4840
|
-
}
|
|
4841
|
-
function sheetBackingSize(o) {
|
|
4842
|
-
return {
|
|
4843
|
-
width: o.columns * o.cellWidth + (o.columns - 1) * o.gutter + o.backingMargin * 2,
|
|
4844
|
-
height: o.rows * o.cellHeight + (o.rows - 1) * o.gutter + o.backingMargin * 2
|
|
4845
|
-
};
|
|
4846
|
-
}
|
|
4847
|
-
function outwardCorner(i, o) {
|
|
4848
|
-
const col = i % o.columns;
|
|
4849
|
-
const row = Math.floor(i / o.columns);
|
|
4850
|
-
const horizontal = col + 0.5 < o.columns / 2 ? "left" : "right";
|
|
4851
|
-
const vertical = row + 0.5 < o.rows / 2 ? "top" : "bottom";
|
|
4852
|
-
return `${vertical}-${horizontal}`;
|
|
4853
|
-
}
|
|
4854
|
-
function tornEdgesOnDetach(i, o) {
|
|
4855
|
-
const col = i % o.columns;
|
|
4856
|
-
const row = Math.floor(i / o.columns);
|
|
4857
|
-
return {
|
|
4858
|
-
top: row > 0 ? "torn" : "intact",
|
|
4859
|
-
bottom: row < o.rows - 1 ? "torn" : "intact",
|
|
4860
|
-
left: col > 0 ? "torn" : "intact",
|
|
4861
|
-
right: col < o.columns - 1 ? "torn" : "intact"
|
|
4862
|
-
};
|
|
4863
|
-
}
|
|
4864
|
-
|
|
4865
|
-
// src/field/slots.ts
|
|
4866
|
-
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
4867
|
-
function effectiveFieldPapers(papers, images) {
|
|
4868
|
-
if (papers) return papers;
|
|
4869
|
-
if (images) {
|
|
4870
|
-
return images.map((src) => ({
|
|
4871
|
-
content: { type: "image", src, fit: "cover" }
|
|
4872
|
-
}));
|
|
4873
|
-
}
|
|
4874
|
-
return Array.from({ length: 12 }, () => ({}));
|
|
4875
|
-
}
|
|
4876
|
-
function groupFieldPapers(papers, fallback) {
|
|
4877
|
-
const groups = /* @__PURE__ */ new Map();
|
|
4878
|
-
papers.forEach((slot, i) => {
|
|
4879
|
-
const config = resolveConfig({ preset: slot.preset ?? fallback });
|
|
4880
|
-
const key = JSON.stringify(config);
|
|
4881
|
-
let group = groups.get(key);
|
|
4882
|
-
if (!group) {
|
|
4883
|
-
group = { config, indices: [], contents: [] };
|
|
4884
|
-
groups.set(key, group);
|
|
4885
|
-
}
|
|
4886
|
-
group.indices.push(i);
|
|
4887
|
-
group.contents.push(slot.content ? contentSchema.parse(slot.content) : config.content);
|
|
4888
|
-
});
|
|
4889
|
-
return [...groups.values()];
|
|
4890
|
-
}
|
|
4891
|
-
function fieldIsInteractive(papers, fallback, explicit) {
|
|
4892
|
-
if (explicit !== void 0) return explicit;
|
|
4893
|
-
return papers.some((s) => s.states) || groupFieldPapers(papers, fallback).some((g) => Boolean(g.config.states));
|
|
4894
|
-
}
|
|
4895
|
-
function resolveFieldSlotConfig(slot, fallback, index, layoutId, layoutOptions) {
|
|
4896
|
-
let config = resolveConfig({ preset: slot.preset ?? fallback });
|
|
4897
|
-
const patch = {};
|
|
4898
|
-
if (slot.content) patch.content = slot.content;
|
|
4899
|
-
if (slot.states) patch.states = slot.states;
|
|
4900
|
-
if (layoutId === "sheet" && config.behavior?.type === "peel" && config.behavior.corner === "auto") {
|
|
4901
|
-
const o = sheetLayoutSchema.parse(layoutOptions);
|
|
4902
|
-
patch.behavior = { corner: outwardCorner(index, o) };
|
|
4903
|
-
}
|
|
4904
|
-
if (Object.keys(patch).length > 0) {
|
|
4905
|
-
config = paperConfigSchema.parse(mergeConfig(config, patch));
|
|
4906
|
-
}
|
|
4907
|
-
return config;
|
|
4908
|
-
}
|
|
4909
|
-
|
|
4910
|
-
// src/content/atlas.ts
|
|
4911
|
-
import * as THREE11 from "three";
|
|
4912
|
-
import { useEffect as useEffect8, useState as useState4 } from "react";
|
|
4913
|
-
var MAX_ATLAS = 4096;
|
|
4914
|
-
function atlasGrid(count, aspect = 1) {
|
|
4915
|
-
const cols = Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count * Math.max(aspect, 0.01)))));
|
|
4916
|
-
return { cols, rows: Math.max(1, Math.ceil(count / cols)) };
|
|
4917
|
-
}
|
|
4918
|
-
function useContentAtlas(contents, sheet2, stock) {
|
|
4919
|
-
const [atlas, setAtlas] = useState4(null);
|
|
4920
|
-
const stableContents = useStable(contents);
|
|
4921
|
-
useEffect8(() => {
|
|
4922
|
-
let disposed = false;
|
|
4923
|
-
const aspect = sheet2.height / sheet2.width;
|
|
4924
|
-
const { cols, rows } = atlasGrid(contents.length, aspect);
|
|
4925
|
-
let tileW = Math.min(1024, Math.floor(MAX_ATLAS / cols));
|
|
4926
|
-
let tileH = Math.round(tileW * aspect);
|
|
4927
|
-
if (tileH * rows > MAX_ATLAS) {
|
|
4928
|
-
tileH = Math.floor(MAX_ATLAS / rows);
|
|
4929
|
-
tileW = Math.max(1, Math.round(tileH / aspect));
|
|
4930
|
-
}
|
|
4931
|
-
const canvas = document.createElement("canvas");
|
|
4932
|
-
canvas.width = tileW * cols;
|
|
4933
|
-
canvas.height = tileH * rows;
|
|
4934
|
-
const ctx = canvas.getContext("2d");
|
|
4935
|
-
ctx.fillStyle = stock.color;
|
|
4936
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
4937
|
-
const texture = new THREE11.CanvasTexture(canvas);
|
|
4938
|
-
texture.colorSpace = THREE11.SRGBColorSpace;
|
|
4939
|
-
texture.anisotropy = 4;
|
|
4940
|
-
setAtlas({ texture, cols, rows });
|
|
4941
|
-
const drawTile = (index, tile) => {
|
|
4942
|
-
if (disposed) return;
|
|
4943
|
-
const x = index % cols * tileW;
|
|
4944
|
-
const y = Math.floor(index / cols) * tileH;
|
|
4945
|
-
ctx.drawImage(tile, x, y, tileW, tileH);
|
|
4946
|
-
texture.needsUpdate = true;
|
|
4947
|
-
};
|
|
4948
|
-
contents.forEach((content, index) => {
|
|
4949
|
-
if (content.type === "image") {
|
|
4950
|
-
const img = new Image();
|
|
4951
|
-
img.crossOrigin = "anonymous";
|
|
4952
|
-
img.onload = () => drawTile(index, renderContentToCanvas(content, sheet2, stock, img));
|
|
4953
|
-
img.src = content.src;
|
|
4954
|
-
} else if (content.type === "text" || content.type === "receipt") {
|
|
4955
|
-
document.fonts.ready.then(() => drawTile(index, renderContentToCanvas(content, sheet2, stock)));
|
|
4956
|
-
} else {
|
|
4957
|
-
drawTile(index, renderContentToCanvas(content, sheet2, stock));
|
|
4958
|
-
}
|
|
4959
|
-
});
|
|
4960
|
-
return () => {
|
|
4961
|
-
disposed = true;
|
|
4962
|
-
texture.dispose();
|
|
4963
|
-
};
|
|
4964
|
-
}, [stableContents, sheet2.width, sheet2.height, stock.id]);
|
|
4965
|
-
return atlas;
|
|
4966
|
-
}
|
|
4967
|
-
|
|
4968
|
-
// src/field/compose.ts
|
|
4969
|
-
function glslType(value) {
|
|
4970
|
-
if (typeof value === "number") return "float";
|
|
4971
|
-
return ["float", "vec2", "vec3", "vec4"][value.length - 1];
|
|
4972
|
-
}
|
|
4973
|
-
function stackUniformValues(stack, sheet2) {
|
|
4974
|
-
const uniforms = { uSheet: [sheet2.width, sheet2.height] };
|
|
4975
|
-
stack.forEach((instance, i) => {
|
|
4976
|
-
if (instance.enabled === false) return;
|
|
4977
|
-
const deformer = getDeformer(instance.type);
|
|
4978
|
-
if (!deformer.glsl) return;
|
|
4979
|
-
const ns = `u${cap(instance.type)}${i}_`;
|
|
4980
|
-
const values = deformer.glsl.uniforms(instance.options);
|
|
4981
|
-
for (const [key, value] of Object.entries(values)) uniforms[ns + key] = value;
|
|
4982
|
-
});
|
|
4983
|
-
return uniforms;
|
|
4984
|
-
}
|
|
4985
|
-
function buildDisplacementGLSL(stack, sheet2) {
|
|
4986
|
-
const decls = ["uniform vec2 uSheet;", "float plBias = 1.0;"];
|
|
4987
|
-
const functions = [];
|
|
4988
|
-
const calls = [];
|
|
4989
|
-
const uniforms = { uSheet: [sheet2.width, sheet2.height] };
|
|
4990
|
-
stack.forEach((instance, i) => {
|
|
4991
|
-
if (instance.enabled === false) return;
|
|
4992
|
-
const deformer = getDeformer(instance.type);
|
|
4993
|
-
if (!deformer.glsl) {
|
|
4994
|
-
throw new Error(
|
|
4995
|
-
`[paperlab] Deformer "${instance.type}" has no GLSL implementation \u2014 it can't run in field mode.`
|
|
4996
|
-
);
|
|
4997
|
-
}
|
|
4998
|
-
const ns = `u${cap(instance.type)}${i}_`;
|
|
4999
|
-
const fn = `pl_${instance.type}${i}`;
|
|
5000
|
-
const values = deformer.glsl.uniforms(instance.options);
|
|
5001
|
-
for (const [key, value] of Object.entries(values)) {
|
|
5002
|
-
decls.push(`uniform ${glslType(value)} ${ns}${key};`);
|
|
5003
|
-
uniforms[ns + key] = value;
|
|
5004
|
-
}
|
|
5005
|
-
const strength = deformer.glsl.strength;
|
|
5006
|
-
functions.push(
|
|
5007
|
-
deformer.glsl.chunk.replaceAll("FN", fn).replace(
|
|
5008
|
-
/U_(\w+)/g,
|
|
5009
|
-
(_, name) => (
|
|
5010
|
-
// The strength uniform reads through the per-instance bias, so one
|
|
5011
|
-
// instanced draw call can bend every sheet by a different amount.
|
|
5012
|
-
name === strength ? `(${ns}${name} * plBias)` : ns + name
|
|
5013
|
-
)
|
|
4807
|
+
{
|
|
4808
|
+
color: hovered && style === "glow" ? "#8ea8ff" : "#5c6f9e",
|
|
4809
|
+
transparent: true,
|
|
4810
|
+
opacity: hovered && style === "glow" ? 0.32 : 0.14,
|
|
4811
|
+
depthWrite: false
|
|
4812
|
+
}
|
|
5014
4813
|
)
|
|
5015
|
-
)
|
|
5016
|
-
|
|
5017
|
-
});
|
|
5018
|
-
const displaceSrc = (
|
|
5019
|
-
/* glsl */
|
|
5020
|
-
`
|
|
5021
|
-
vec3 plDisplace(vec3 p, vec2 uv, float t, float bias) {
|
|
5022
|
-
plBias = bias;
|
|
5023
|
-
vec3 q = p;
|
|
5024
|
-
${calls.join("\n ")}
|
|
5025
|
-
return q;
|
|
5026
|
-
}
|
|
5027
|
-
`
|
|
5028
|
-
);
|
|
5029
|
-
return { functionsSrc: `${decls.join("\n")}
|
|
5030
|
-
${functions.join("\n")}`, displaceSrc, uniforms };
|
|
4814
|
+
] }),
|
|
4815
|
+
/* @__PURE__ */ jsx6("lineSegments", { geometry: edges, children: /* @__PURE__ */ jsx6("lineBasicMaterial", { color: hovered ? "#aebfff" : "#6b7da8" }) })
|
|
4816
|
+
] });
|
|
5031
4817
|
}
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
4818
|
+
|
|
4819
|
+
// src/field/sheetGrid.ts
|
|
4820
|
+
import { z as z23 } from "zod";
|
|
4821
|
+
var sheetLayoutSchema = z23.object({
|
|
4822
|
+
rows: z23.number().int().min(1).max(12).default(2),
|
|
4823
|
+
columns: z23.number().int().min(1).max(12).default(5),
|
|
4824
|
+
/** World-units gap between slots. Stamps are printed in register — no jitter. */
|
|
4825
|
+
gutter: z23.number().min(0).max(1).default(0.08),
|
|
4826
|
+
/** Slot footprint in world units (the paper preset should match). */
|
|
4827
|
+
cellWidth: z23.number().min(0.1).max(4).default(0.72),
|
|
4828
|
+
cellHeight: z23.number().min(0.1).max(4).default(0.86),
|
|
4829
|
+
/** Render the shared backing sheet behind the grid. */
|
|
4830
|
+
backing: z23.boolean().default(true),
|
|
4831
|
+
backingMargin: z23.number().min(0).max(1).default(0.12)
|
|
4832
|
+
});
|
|
4833
|
+
var SHEET_LIFT = 0.012;
|
|
4834
|
+
function withSheetCellFromPaper(parsed, rawOptions, paperDims) {
|
|
4835
|
+
if (!paperDims) return parsed;
|
|
4836
|
+
const hasW = rawOptions !== void 0 && rawOptions.cellWidth !== void 0;
|
|
4837
|
+
const hasH = rawOptions !== void 0 && rawOptions.cellHeight !== void 0;
|
|
4838
|
+
if (hasW && hasH) return parsed;
|
|
4839
|
+
return {
|
|
4840
|
+
...parsed,
|
|
4841
|
+
cellWidth: hasW ? parsed.cellWidth : paperDims.width,
|
|
4842
|
+
cellHeight: hasH ? parsed.cellHeight : paperDims.height
|
|
4843
|
+
};
|
|
5057
4844
|
}
|
|
5058
|
-
|
|
5059
|
-
|
|
4845
|
+
function sheetSlotXY(i, o) {
|
|
4846
|
+
const col = i % o.columns;
|
|
4847
|
+
const row = Math.floor(i / o.columns);
|
|
4848
|
+
return {
|
|
4849
|
+
x: (col - (o.columns - 1) / 2) * (o.cellWidth + o.gutter),
|
|
4850
|
+
y: ((o.rows - 1) / 2 - row) * (o.cellHeight + o.gutter)
|
|
4851
|
+
};
|
|
5060
4852
|
}
|
|
5061
|
-
function
|
|
5062
|
-
return
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
uniform vec2 uAtlasGrid;
|
|
5067
|
-
uniform float uBackDarken;
|
|
5068
|
-
uniform vec3 uStockColor;
|
|
5069
|
-
uniform float uShowThrough;
|
|
5070
|
-
varying vec2 vPaperUv;
|
|
5071
|
-
varying float vAtlas;
|
|
5072
|
-
${TRANSLUCENCY_FRAGMENT}
|
|
5073
|
-
void main() {
|
|
5074
|
-
float col = mod(vAtlas, uAtlasGrid.x);
|
|
5075
|
-
float row = floor(vAtlas / uAtlasGrid.x);
|
|
5076
|
-
vec2 tiled = (vPaperUv + vec2(col, uAtlasGrid.y - 1.0 - row)) / uAtlasGrid;
|
|
5077
|
-
vec4 front = texture2D(uAtlas, tiled);
|
|
5078
|
-
if (gl_FrontFacing) {
|
|
5079
|
-
csm_DiffuseColor = front;
|
|
5080
|
-
} else {
|
|
5081
|
-
csm_DiffuseColor = vec4(uStockColor * mix(vec3(1.0), front.rgb, uShowThrough), 1.0);
|
|
5082
|
-
csm_DiffuseColor.rgb *= uBackDarken;
|
|
5083
|
-
}
|
|
5084
|
-
// Light coming through the sheet, filtered by what is printed on it. Same
|
|
5085
|
-
// ink either side \u2014 the light passes through the same fibres regardless of
|
|
5086
|
-
// which face happens to be turned toward the camera.
|
|
5087
|
-
csm_Emissive = plTransmission(front.rgb);
|
|
4853
|
+
function sheetBackingSize(o) {
|
|
4854
|
+
return {
|
|
4855
|
+
width: o.columns * o.cellWidth + (o.columns - 1) * o.gutter + o.backingMargin * 2,
|
|
4856
|
+
height: o.rows * o.cellHeight + (o.rows - 1) * o.gutter + o.backingMargin * 2
|
|
4857
|
+
};
|
|
5088
4858
|
}
|
|
5089
|
-
|
|
5090
|
-
|
|
4859
|
+
function outwardCorner(i, o) {
|
|
4860
|
+
const col = i % o.columns;
|
|
4861
|
+
const row = Math.floor(i / o.columns);
|
|
4862
|
+
const horizontal = col + 0.5 < o.columns / 2 ? "left" : "right";
|
|
4863
|
+
const vertical = row + 0.5 < o.rows / 2 ? "top" : "bottom";
|
|
4864
|
+
return `${vertical}-${horizontal}`;
|
|
5091
4865
|
}
|
|
5092
|
-
function
|
|
5093
|
-
|
|
4866
|
+
function tornEdgesOnDetach(i, o) {
|
|
4867
|
+
const col = i % o.columns;
|
|
4868
|
+
const row = Math.floor(i / o.columns);
|
|
4869
|
+
return {
|
|
4870
|
+
top: row > 0 ? "torn" : "intact",
|
|
4871
|
+
bottom: row < o.rows - 1 ? "torn" : "intact",
|
|
4872
|
+
left: col > 0 ? "torn" : "intact",
|
|
4873
|
+
right: col < o.columns - 1 ? "torn" : "intact"
|
|
4874
|
+
};
|
|
5094
4875
|
}
|
|
5095
4876
|
|
|
5096
4877
|
// src/stage/path.ts
|
|
@@ -5619,228 +5400,249 @@ registerLayout(rack);
|
|
|
5619
5400
|
registerLayout(colonnade);
|
|
5620
5401
|
registerLayout(sheet);
|
|
5621
5402
|
|
|
5622
|
-
// src/
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5403
|
+
// src/PaperField.tsx
|
|
5404
|
+
import * as THREE15 from "three";
|
|
5405
|
+
import { gsap as gsap5 } from "gsap";
|
|
5406
|
+
import { Canvas, useFrame as useFrame5, useThree as useThree4 } from "@react-three/fiber";
|
|
5407
|
+
import { forwardRef as forwardRef2, useEffect as useEffect12, useMemo as useMemo9, useRef as useRef8 } from "react";
|
|
5408
|
+
|
|
5409
|
+
// src/field/slots.ts
|
|
5410
|
+
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
5411
|
+
function effectiveFieldPapers(papers, images) {
|
|
5412
|
+
if (papers) return papers;
|
|
5413
|
+
if (images) {
|
|
5414
|
+
return images.map((src) => ({
|
|
5415
|
+
content: { type: "image", src, fit: "cover" }
|
|
5416
|
+
}));
|
|
5635
5417
|
}
|
|
5636
|
-
return
|
|
5637
|
-
}
|
|
5638
|
-
function lightenHex(hex, amount) {
|
|
5639
|
-
const n = parseInt(hex.replace("#", ""), 16);
|
|
5640
|
-
const ch = (shift) => {
|
|
5641
|
-
const c = n >> shift & 255;
|
|
5642
|
-
return Math.min(255, Math.round(c + (255 - c) * amount));
|
|
5643
|
-
};
|
|
5644
|
-
return `#${(ch(16) << 16 | ch(8) << 8 | ch(0)).toString(16).padStart(6, "0")}`;
|
|
5418
|
+
return Array.from({ length: 12 }, () => ({}));
|
|
5645
5419
|
}
|
|
5646
|
-
function
|
|
5647
|
-
const
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
const x = r.x * cw;
|
|
5656
|
-
const y = r.y * chh;
|
|
5657
|
-
const w = r.w * cw;
|
|
5658
|
-
const h = r.h * chh;
|
|
5659
|
-
const radius = Math.min(w, h) * 0.06;
|
|
5660
|
-
ctx.fillStyle = lightenHex(spec.tint, removed ? 0.5 : 0.07);
|
|
5661
|
-
roundedRect(ctx, x, y, w, h, radius);
|
|
5662
|
-
ctx.fill();
|
|
5663
|
-
if (removed) {
|
|
5664
|
-
const sheen = ctx.createLinearGradient(x, y, x + w, y + h);
|
|
5665
|
-
sheen.addColorStop(0, "rgba(255,255,255,0)");
|
|
5666
|
-
sheen.addColorStop(0.5, "rgba(255,255,255,0.35)");
|
|
5667
|
-
sheen.addColorStop(1, "rgba(255,255,255,0)");
|
|
5668
|
-
ctx.fillStyle = sheen;
|
|
5669
|
-
roundedRect(ctx, x, y, w, h, radius);
|
|
5670
|
-
ctx.fill();
|
|
5420
|
+
function groupFieldPapers(papers, fallback) {
|
|
5421
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5422
|
+
papers.forEach((slot, i) => {
|
|
5423
|
+
const config = resolveConfig({ preset: slot.preset ?? fallback });
|
|
5424
|
+
const key = JSON.stringify(config);
|
|
5425
|
+
let group = groups.get(key);
|
|
5426
|
+
if (!group) {
|
|
5427
|
+
group = { config, indices: [], contents: [] };
|
|
5428
|
+
groups.set(key, group);
|
|
5671
5429
|
}
|
|
5430
|
+
group.indices.push(i);
|
|
5431
|
+
group.contents.push(slot.content ? contentSchema.parse(slot.content) : config.content);
|
|
5672
5432
|
});
|
|
5433
|
+
return [...groups.values()];
|
|
5673
5434
|
}
|
|
5674
|
-
function
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
5678
|
-
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
5679
|
-
ctx.arcTo(x, y + h, x, y, r);
|
|
5680
|
-
ctx.arcTo(x, y, x + w, y, r);
|
|
5681
|
-
ctx.closePath();
|
|
5435
|
+
function fieldIsInteractive(papers, fallback, explicit) {
|
|
5436
|
+
if (explicit !== void 0) return explicit;
|
|
5437
|
+
return papers.some((s) => s.states) || groupFieldPapers(papers, fallback).some((g) => Boolean(g.config.states));
|
|
5682
5438
|
}
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
if (
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
}
|
|
5692
|
-
return { carry: carry2, handled: false };
|
|
5693
|
-
}
|
|
5694
|
-
if (carry2.slot !== slot) return { carry: carry2, handled: false };
|
|
5695
|
-
const zoneCount = Math.max(controller.zoneIds().length, 1);
|
|
5696
|
-
if (key === "ArrowRight" || key === "ArrowDown") {
|
|
5697
|
-
return { carry: { ...carry2, zoneIndex: (carry2.zoneIndex + 1) % zoneCount }, handled: true };
|
|
5698
|
-
}
|
|
5699
|
-
if (key === "ArrowLeft" || key === "ArrowUp") {
|
|
5700
|
-
return {
|
|
5701
|
-
carry: { ...carry2, zoneIndex: (carry2.zoneIndex - 1 + zoneCount) % zoneCount },
|
|
5702
|
-
handled: true
|
|
5703
|
-
};
|
|
5704
|
-
}
|
|
5705
|
-
if (key === "Enter" || key === " ") {
|
|
5706
|
-
const zone = controller.zoneIds()[carry2.zoneIndex];
|
|
5707
|
-
if (zone) controller.placeAtZone(slot, zone);
|
|
5708
|
-
return { carry: null, handled: true };
|
|
5439
|
+
function resolveFieldSlotConfig(slot, fallback, index, layoutId, layoutOptions) {
|
|
5440
|
+
let config = resolveConfig({ preset: slot.preset ?? fallback });
|
|
5441
|
+
const patch = {};
|
|
5442
|
+
if (slot.content) patch.content = slot.content;
|
|
5443
|
+
if (slot.states) patch.states = slot.states;
|
|
5444
|
+
if (layoutId === "sheet" && config.behavior?.type === "peel" && config.behavior.corner === "auto") {
|
|
5445
|
+
const o = sheetLayoutSchema.parse(layoutOptions);
|
|
5446
|
+
patch.behavior = { corner: outwardCorner(index, o) };
|
|
5709
5447
|
}
|
|
5710
|
-
if (
|
|
5711
|
-
|
|
5712
|
-
return { carry: null, handled: true };
|
|
5448
|
+
if (Object.keys(patch).length > 0) {
|
|
5449
|
+
config = paperConfigSchema.parse(mergeConfig(config, patch));
|
|
5713
5450
|
}
|
|
5714
|
-
return
|
|
5451
|
+
return config;
|
|
5715
5452
|
}
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
};
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5453
|
+
|
|
5454
|
+
// src/field/fieldGroup.tsx
|
|
5455
|
+
import * as THREE12 from "three";
|
|
5456
|
+
import { gsap as gsap3 } from "gsap";
|
|
5457
|
+
import { useFrame as useFrame3 } from "@react-three/fiber";
|
|
5458
|
+
import { useEffect as useEffect9, useMemo as useMemo6, useRef as useRef6 } from "react";
|
|
5459
|
+
import CustomShaderMaterial2 from "three-custom-shader-material";
|
|
5460
|
+
|
|
5461
|
+
// src/content/atlas.ts
|
|
5462
|
+
import * as THREE11 from "three";
|
|
5463
|
+
import { useEffect as useEffect8, useState as useState4 } from "react";
|
|
5464
|
+
var MAX_ATLAS = 4096;
|
|
5465
|
+
function atlasGrid(count, aspect = 1) {
|
|
5466
|
+
const cols = Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count * Math.max(aspect, 0.01)))));
|
|
5467
|
+
return { cols, rows: Math.max(1, Math.ceil(count / cols)) };
|
|
5468
|
+
}
|
|
5469
|
+
function useContentAtlas(contents, sheet2, stock) {
|
|
5470
|
+
const [atlas, setAtlas] = useState4(null);
|
|
5471
|
+
const stableContents = useStable(contents);
|
|
5472
|
+
useEffect8(() => {
|
|
5473
|
+
let disposed = false;
|
|
5474
|
+
const aspect = sheet2.height / sheet2.width;
|
|
5475
|
+
const { cols, rows } = atlasGrid(contents.length, aspect);
|
|
5476
|
+
let tileW = Math.min(1024, Math.floor(MAX_ATLAS / cols));
|
|
5477
|
+
let tileH = Math.round(tileW * aspect);
|
|
5478
|
+
if (tileH * rows > MAX_ATLAS) {
|
|
5479
|
+
tileH = Math.floor(MAX_ATLAS / rows);
|
|
5480
|
+
tileW = Math.max(1, Math.round(tileH / aspect));
|
|
5739
5481
|
}
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
)
|
|
5769
|
-
|
|
5482
|
+
const canvas = document.createElement("canvas");
|
|
5483
|
+
canvas.width = tileW * cols;
|
|
5484
|
+
canvas.height = tileH * rows;
|
|
5485
|
+
const ctx = canvas.getContext("2d");
|
|
5486
|
+
ctx.fillStyle = stock.color;
|
|
5487
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
5488
|
+
const texture = new THREE11.CanvasTexture(canvas);
|
|
5489
|
+
texture.colorSpace = THREE11.SRGBColorSpace;
|
|
5490
|
+
texture.anisotropy = 4;
|
|
5491
|
+
setAtlas({ texture, cols, rows });
|
|
5492
|
+
const drawTile = (index, tile) => {
|
|
5493
|
+
if (disposed) return;
|
|
5494
|
+
const x = index % cols * tileW;
|
|
5495
|
+
const y = Math.floor(index / cols) * tileH;
|
|
5496
|
+
ctx.drawImage(tile, x, y, tileW, tileH);
|
|
5497
|
+
texture.needsUpdate = true;
|
|
5498
|
+
};
|
|
5499
|
+
contents.forEach((content, index) => {
|
|
5500
|
+
if (content.type === "image") {
|
|
5501
|
+
const img = new Image();
|
|
5502
|
+
img.crossOrigin = "anonymous";
|
|
5503
|
+
img.onload = () => drawTile(index, renderContentToCanvas(content, sheet2, stock, img));
|
|
5504
|
+
img.src = content.src;
|
|
5505
|
+
} else if (content.type === "text" || content.type === "receipt") {
|
|
5506
|
+
document.fonts.ready.then(() => drawTile(index, renderContentToCanvas(content, sheet2, stock)));
|
|
5507
|
+
} else {
|
|
5508
|
+
drawTile(index, renderContentToCanvas(content, sheet2, stock));
|
|
5509
|
+
}
|
|
5510
|
+
});
|
|
5511
|
+
return () => {
|
|
5512
|
+
disposed = true;
|
|
5513
|
+
texture.dispose();
|
|
5514
|
+
};
|
|
5515
|
+
}, [stableContents, sheet2.width, sheet2.height, stock.id]);
|
|
5516
|
+
return atlas;
|
|
5770
5517
|
}
|
|
5771
5518
|
|
|
5772
|
-
// src/field/
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
...layout.defaults,
|
|
5777
|
-
...propOptions
|
|
5778
|
-
});
|
|
5779
|
-
if (layoutId !== "sheet") return parsed;
|
|
5780
|
-
return withSheetCellFromPaper(
|
|
5781
|
-
parsed,
|
|
5782
|
-
propOptions,
|
|
5783
|
-
firstSheet
|
|
5784
|
-
);
|
|
5519
|
+
// src/field/compose.ts
|
|
5520
|
+
function glslType(value) {
|
|
5521
|
+
if (typeof value === "number") return "float";
|
|
5522
|
+
return ["float", "vec2", "vec3", "vec4"][value.length - 1];
|
|
5785
5523
|
}
|
|
5786
|
-
function
|
|
5787
|
-
const
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
max[axis] = Math.max(max[axis], pose.position[axis] + r);
|
|
5798
|
-
}
|
|
5799
|
-
}
|
|
5800
|
-
}
|
|
5801
|
-
return {
|
|
5802
|
-
center: [(min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2],
|
|
5803
|
-
half: [(max[0] - min[0]) / 2, (max[1] - min[1]) / 2, (max[2] - min[2]) / 2]
|
|
5804
|
-
};
|
|
5524
|
+
function stackUniformValues(stack, sheet2) {
|
|
5525
|
+
const uniforms = { uSheet: [sheet2.width, sheet2.height] };
|
|
5526
|
+
stack.forEach((instance, i) => {
|
|
5527
|
+
if (instance.enabled === false) return;
|
|
5528
|
+
const deformer = getDeformer(instance.type);
|
|
5529
|
+
if (!deformer.glsl) return;
|
|
5530
|
+
const ns = `u${cap(instance.type)}${i}_`;
|
|
5531
|
+
const values = deformer.glsl.uniforms(instance.options);
|
|
5532
|
+
for (const [key, value] of Object.entries(values)) uniforms[ns + key] = value;
|
|
5533
|
+
});
|
|
5534
|
+
return uniforms;
|
|
5805
5535
|
}
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
const
|
|
5810
|
-
const
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
const r = reach * Math.max(pose.scale, 0);
|
|
5818
|
-
const depth = pose.position[2] - center[2];
|
|
5819
|
-
distance = Math.max(
|
|
5820
|
-
distance,
|
|
5821
|
-
(Math.abs(pose.position[0] - center[0]) + r) / hTan + depth,
|
|
5822
|
-
(Math.abs(pose.position[1] - center[1]) + r) / vTan + depth
|
|
5536
|
+
function buildDisplacementGLSL(stack, sheet2) {
|
|
5537
|
+
const decls = ["uniform vec2 uSheet;", "float plBias = 1.0;"];
|
|
5538
|
+
const functions = [];
|
|
5539
|
+
const calls = [];
|
|
5540
|
+
const uniforms = { uSheet: [sheet2.width, sheet2.height] };
|
|
5541
|
+
stack.forEach((instance, i) => {
|
|
5542
|
+
if (instance.enabled === false) return;
|
|
5543
|
+
const deformer = getDeformer(instance.type);
|
|
5544
|
+
if (!deformer.glsl) {
|
|
5545
|
+
throw new Error(
|
|
5546
|
+
`[paperlab] Deformer "${instance.type}" has no GLSL implementation \u2014 it can't run in field mode.`
|
|
5823
5547
|
);
|
|
5824
5548
|
}
|
|
5549
|
+
const ns = `u${cap(instance.type)}${i}_`;
|
|
5550
|
+
const fn = `pl_${instance.type}${i}`;
|
|
5551
|
+
const values = deformer.glsl.uniforms(instance.options);
|
|
5552
|
+
for (const [key, value] of Object.entries(values)) {
|
|
5553
|
+
decls.push(`uniform ${glslType(value)} ${ns}${key};`);
|
|
5554
|
+
uniforms[ns + key] = value;
|
|
5555
|
+
}
|
|
5556
|
+
const strength = deformer.glsl.strength;
|
|
5557
|
+
functions.push(
|
|
5558
|
+
deformer.glsl.chunk.replaceAll("FN", fn).replace(
|
|
5559
|
+
/U_(\w+)/g,
|
|
5560
|
+
(_, name) => (
|
|
5561
|
+
// The strength uniform reads through the per-instance bias, so one
|
|
5562
|
+
// instanced draw call can bend every sheet by a different amount.
|
|
5563
|
+
name === strength ? `(${ns}${name} * plBias)` : ns + name
|
|
5564
|
+
)
|
|
5565
|
+
)
|
|
5566
|
+
);
|
|
5567
|
+
calls.push(`${fn}(q, uv, t);`);
|
|
5568
|
+
});
|
|
5569
|
+
const displaceSrc = (
|
|
5570
|
+
/* glsl */
|
|
5571
|
+
`
|
|
5572
|
+
vec3 plDisplace(vec3 p, vec2 uv, float t, float bias) {
|
|
5573
|
+
plBias = bias;
|
|
5574
|
+
vec3 q = p;
|
|
5575
|
+
${calls.join("\n ")}
|
|
5576
|
+
return q;
|
|
5577
|
+
}
|
|
5578
|
+
`
|
|
5579
|
+
);
|
|
5580
|
+
return { functionsSrc: `${decls.join("\n")}
|
|
5581
|
+
${functions.join("\n")}`, displaceSrc, uniforms };
|
|
5582
|
+
}
|
|
5583
|
+
function buildFieldVertexShader(composed) {
|
|
5584
|
+
return (
|
|
5585
|
+
/* glsl */
|
|
5586
|
+
`
|
|
5587
|
+
uniform float uPlTime;
|
|
5588
|
+
attribute float aPhase;
|
|
5589
|
+
attribute float aAtlas;
|
|
5590
|
+
attribute float aBias;
|
|
5591
|
+
varying vec2 vPaperUv;
|
|
5592
|
+
varying float vAtlas;
|
|
5593
|
+
${TRANSLUCENCY_VARYINGS}
|
|
5594
|
+
${composed.functionsSrc}
|
|
5595
|
+
${composed.displaceSrc}
|
|
5596
|
+
void main() {
|
|
5597
|
+
float t = uPlTime + aPhase;
|
|
5598
|
+
vec3 p = plDisplace(position, uv, t, aBias);
|
|
5599
|
+
vec2 step = uSheet * 0.01;
|
|
5600
|
+
vec3 px = plDisplace(position + vec3(step.x, 0.0, 0.0), uv + vec2(0.01, 0.0), t, aBias);
|
|
5601
|
+
vec3 py = plDisplace(position + vec3(0.0, step.y, 0.0), uv + vec2(0.0, 0.01), t, aBias);
|
|
5602
|
+
vec3 n = cross(px - p, py - p);
|
|
5603
|
+
csm_Normal = length(n) > 1e-12 ? normalize(n) : vec3(0.0, 0.0, 1.0);
|
|
5604
|
+
csm_Position = p;
|
|
5605
|
+
${translucencyVertexChunk({ model: "modelMatrix * instanceMatrix", position: "p", normal: "csm_Normal" })}
|
|
5606
|
+
vPaperUv = uv;
|
|
5607
|
+
vAtlas = aAtlas;
|
|
5608
|
+
}
|
|
5609
|
+
`
|
|
5610
|
+
);
|
|
5611
|
+
}
|
|
5612
|
+
function buildFieldFragmentShader() {
|
|
5613
|
+
return (
|
|
5614
|
+
/* glsl */
|
|
5615
|
+
`
|
|
5616
|
+
uniform sampler2D uAtlas;
|
|
5617
|
+
uniform vec2 uAtlasGrid;
|
|
5618
|
+
uniform float uBackDarken;
|
|
5619
|
+
uniform vec3 uStockColor;
|
|
5620
|
+
uniform float uShowThrough;
|
|
5621
|
+
varying vec2 vPaperUv;
|
|
5622
|
+
varying float vAtlas;
|
|
5623
|
+
${TRANSLUCENCY_FRAGMENT}
|
|
5624
|
+
void main() {
|
|
5625
|
+
float col = mod(vAtlas, uAtlasGrid.x);
|
|
5626
|
+
float row = floor(vAtlas / uAtlasGrid.x);
|
|
5627
|
+
vec2 tiled = (vPaperUv + vec2(col, uAtlasGrid.y - 1.0 - row)) / uAtlasGrid;
|
|
5628
|
+
vec4 front = texture2D(uAtlas, tiled);
|
|
5629
|
+
if (gl_FrontFacing) {
|
|
5630
|
+
csm_DiffuseColor = front;
|
|
5631
|
+
} else {
|
|
5632
|
+
csm_DiffuseColor = vec4(uStockColor * mix(vec3(1.0), front.rgb, uShowThrough), 1.0);
|
|
5633
|
+
csm_DiffuseColor.rgb *= uBackDarken;
|
|
5825
5634
|
}
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5635
|
+
// Light coming through the sheet, filtered by what is printed on it. Same
|
|
5636
|
+
// ink either side \u2014 the light passes through the same fibres regardless of
|
|
5637
|
+
// which face happens to be turned toward the camera.
|
|
5638
|
+
csm_Emissive = plTransmission(front.rgb);
|
|
5639
|
+
}
|
|
5640
|
+
`
|
|
5641
|
+
);
|
|
5642
|
+
}
|
|
5643
|
+
function cap(s) {
|
|
5644
|
+
return s.charAt(0).toUpperCase() + s.slice(1).replace(/-(\w)/g, (_, c) => c.toUpperCase());
|
|
5830
5645
|
}
|
|
5831
|
-
|
|
5832
|
-
// src/PaperField.tsx
|
|
5833
|
-
import * as THREE15 from "three";
|
|
5834
|
-
import { gsap as gsap5 } from "gsap";
|
|
5835
|
-
import { Canvas, useFrame as useFrame5, useThree as useThree4 } from "@react-three/fiber";
|
|
5836
|
-
import { forwardRef as forwardRef2, useEffect as useEffect12, useMemo as useMemo9, useRef as useRef8 } from "react";
|
|
5837
|
-
|
|
5838
|
-
// src/field/fieldGroup.tsx
|
|
5839
|
-
import * as THREE12 from "three";
|
|
5840
|
-
import { gsap as gsap3 } from "gsap";
|
|
5841
|
-
import { useFrame as useFrame3 } from "@react-three/fiber";
|
|
5842
|
-
import { useEffect as useEffect9, useMemo as useMemo6, useRef as useRef6 } from "react";
|
|
5843
|
-
import CustomShaderMaterial2 from "three-custom-shader-material";
|
|
5844
5646
|
|
|
5845
5647
|
// src/field/stack.ts
|
|
5846
5648
|
function fieldShapeStack(config, progress) {
|
|
@@ -5852,7 +5654,7 @@ function fieldShapeStack(config, progress) {
|
|
|
5852
5654
|
}
|
|
5853
5655
|
|
|
5854
5656
|
// src/field/fieldGroup.tsx
|
|
5855
|
-
import { jsx as
|
|
5657
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
5856
5658
|
var scratchObj = new THREE12.Object3D();
|
|
5857
5659
|
var scratchAero = { position: [0, 0, 0], rotation: [0, 0, 0] };
|
|
5858
5660
|
var FIELD_SEGMENT_CAP = 48;
|
|
@@ -6055,7 +5857,7 @@ function FieldGroup({
|
|
|
6055
5857
|
});
|
|
6056
5858
|
return (
|
|
6057
5859
|
// biome-ignore lint/a11y/noStaticElementInteractions: An R3F <instancedMesh> is a three.js object, not a DOM node — it has no role to give and no keyboard to receive. The keyboard route into the same action is on the canvas, which `useWalk` makes focusable and drives with the arrow keys.
|
|
6058
|
-
/* @__PURE__ */
|
|
5860
|
+
/* @__PURE__ */ jsx7(
|
|
6059
5861
|
"instancedMesh",
|
|
6060
5862
|
{
|
|
6061
5863
|
ref: meshRef,
|
|
@@ -6071,7 +5873,7 @@ function FieldGroup({
|
|
|
6071
5873
|
event.stopPropagation();
|
|
6072
5874
|
onSelect(paper);
|
|
6073
5875
|
}),
|
|
6074
|
-
children: /* @__PURE__ */
|
|
5876
|
+
children: /* @__PURE__ */ jsx7(
|
|
6075
5877
|
CustomShaderMaterial2,
|
|
6076
5878
|
{
|
|
6077
5879
|
baseMaterial: THREE12.MeshStandardMaterial,
|
|
@@ -6128,7 +5930,71 @@ var easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
|
6128
5930
|
// src/field/backingSheet.tsx
|
|
6129
5931
|
import * as THREE13 from "three";
|
|
6130
5932
|
import { useEffect as useEffect10, useMemo as useMemo7 } from "react";
|
|
6131
|
-
|
|
5933
|
+
|
|
5934
|
+
// src/content/backing.ts
|
|
5935
|
+
function silhouetteRects(o, count) {
|
|
5936
|
+
const { width, height } = sheetBackingSize(o);
|
|
5937
|
+
const rects = [];
|
|
5938
|
+
for (let i = 0; i < count; i++) {
|
|
5939
|
+
const { x, y } = sheetSlotXY(i, o);
|
|
5940
|
+
rects.push({
|
|
5941
|
+
x: (x - o.cellWidth / 2 + width / 2) / width,
|
|
5942
|
+
// World y up → canvas y down.
|
|
5943
|
+
y: (height / 2 - y - o.cellHeight / 2) / height,
|
|
5944
|
+
w: o.cellWidth / width,
|
|
5945
|
+
h: o.cellHeight / height
|
|
5946
|
+
});
|
|
5947
|
+
}
|
|
5948
|
+
return rects;
|
|
5949
|
+
}
|
|
5950
|
+
function lightenHex(hex, amount) {
|
|
5951
|
+
const n = parseInt(hex.replace("#", ""), 16);
|
|
5952
|
+
const ch = (shift) => {
|
|
5953
|
+
const c = n >> shift & 255;
|
|
5954
|
+
return Math.min(255, Math.round(c + (255 - c) * amount));
|
|
5955
|
+
};
|
|
5956
|
+
return `#${(ch(16) << 16 | ch(8) << 8 | ch(0)).toString(16).padStart(6, "0")}`;
|
|
5957
|
+
}
|
|
5958
|
+
function drawBacking(canvas, spec) {
|
|
5959
|
+
const ctx = canvas.getContext("2d");
|
|
5960
|
+
if (!ctx) return;
|
|
5961
|
+
const { width: cw, height: chh } = canvas;
|
|
5962
|
+
ctx.fillStyle = spec.tint;
|
|
5963
|
+
ctx.fillRect(0, 0, cw, chh);
|
|
5964
|
+
const rects = silhouetteRects(spec.options, spec.count);
|
|
5965
|
+
rects.forEach((r, i) => {
|
|
5966
|
+
const removed = spec.removed.has(i);
|
|
5967
|
+
const x = r.x * cw;
|
|
5968
|
+
const y = r.y * chh;
|
|
5969
|
+
const w = r.w * cw;
|
|
5970
|
+
const h = r.h * chh;
|
|
5971
|
+
const radius = Math.min(w, h) * 0.06;
|
|
5972
|
+
ctx.fillStyle = lightenHex(spec.tint, removed ? 0.5 : 0.07);
|
|
5973
|
+
roundedRect(ctx, x, y, w, h, radius);
|
|
5974
|
+
ctx.fill();
|
|
5975
|
+
if (removed) {
|
|
5976
|
+
const sheen = ctx.createLinearGradient(x, y, x + w, y + h);
|
|
5977
|
+
sheen.addColorStop(0, "rgba(255,255,255,0)");
|
|
5978
|
+
sheen.addColorStop(0.5, "rgba(255,255,255,0.35)");
|
|
5979
|
+
sheen.addColorStop(1, "rgba(255,255,255,0)");
|
|
5980
|
+
ctx.fillStyle = sheen;
|
|
5981
|
+
roundedRect(ctx, x, y, w, h, radius);
|
|
5982
|
+
ctx.fill();
|
|
5983
|
+
}
|
|
5984
|
+
});
|
|
5985
|
+
}
|
|
5986
|
+
function roundedRect(ctx, x, y, w, h, r) {
|
|
5987
|
+
ctx.beginPath();
|
|
5988
|
+
ctx.moveTo(x + r, y);
|
|
5989
|
+
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
5990
|
+
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
5991
|
+
ctx.arcTo(x, y + h, x, y, r);
|
|
5992
|
+
ctx.arcTo(x, y, x + w, y, r);
|
|
5993
|
+
ctx.closePath();
|
|
5994
|
+
}
|
|
5995
|
+
|
|
5996
|
+
// src/field/backingSheet.tsx
|
|
5997
|
+
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
6132
5998
|
var BACKING_TINT = "#f3ecdd";
|
|
6133
5999
|
function BackingSheet({
|
|
6134
6000
|
options,
|
|
@@ -6152,9 +6018,9 @@ function BackingSheet({
|
|
|
6152
6018
|
texture.needsUpdate = true;
|
|
6153
6019
|
}, [canvas, texture, JSON.stringify(options), count, removedKey]);
|
|
6154
6020
|
useEffect10(() => () => texture?.dispose(), [texture]);
|
|
6155
|
-
return /* @__PURE__ */
|
|
6156
|
-
/* @__PURE__ */
|
|
6157
|
-
/* @__PURE__ */
|
|
6021
|
+
return /* @__PURE__ */ jsxs4("mesh", { receiveShadow: true, children: [
|
|
6022
|
+
/* @__PURE__ */ jsx8("planeGeometry", { args: [width, height] }),
|
|
6023
|
+
/* @__PURE__ */ jsx8("meshStandardMaterial", { map: texture, color: "#ffffff", roughness: 0.92, metalness: 0 })
|
|
6158
6024
|
] });
|
|
6159
6025
|
}
|
|
6160
6026
|
|
|
@@ -6162,8 +6028,8 @@ function BackingSheet({
|
|
|
6162
6028
|
import * as THREE14 from "three";
|
|
6163
6029
|
import { gsap as gsap4 } from "gsap";
|
|
6164
6030
|
import { useFrame as useFrame4, useThree as useThree3 } from "@react-three/fiber";
|
|
6165
|
-
import { useContext as useContext3, useEffect as useEffect11, useMemo as useMemo8, useRef as useRef7, useState as
|
|
6166
|
-
import { jsx as
|
|
6031
|
+
import { useContext as useContext3, useEffect as useEffect11, useMemo as useMemo8, useRef as useRef7, useState as useState5 } from "react";
|
|
6032
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
6167
6033
|
var PICK_BEHAVIORS = /* @__PURE__ */ new Set(["peel", "carry"]);
|
|
6168
6034
|
function InteractiveField(props) {
|
|
6169
6035
|
const { papers, fallback, layoutId, layoutOptions, sheetOptions } = props;
|
|
@@ -6175,9 +6041,9 @@ function InteractiveField(props) {
|
|
|
6175
6041
|
const camera = useThree3((s) => s.camera);
|
|
6176
6042
|
const gl = useThree3((s) => s.gl);
|
|
6177
6043
|
const controls = useThree3((s) => s.controls);
|
|
6178
|
-
const [removed, setRemoved] =
|
|
6179
|
-
const [slotPatches, setSlotPatches] =
|
|
6180
|
-
const [slotStates, setSlotStates] =
|
|
6044
|
+
const [removed, setRemoved] = useState5(EMPTY_SET);
|
|
6045
|
+
const [slotPatches, setSlotPatches] = useState5({});
|
|
6046
|
+
const [slotStates, setSlotStates] = useState5({});
|
|
6181
6047
|
const slotConfigs = useMemo8(
|
|
6182
6048
|
() => papers.map((slot, i) => {
|
|
6183
6049
|
const config = resolveFieldSlotConfig(slot, fallback, i, layoutId, layoutOptions);
|
|
@@ -6470,12 +6336,12 @@ function InteractiveField(props) {
|
|
|
6470
6336
|
ref.current = null;
|
|
6471
6337
|
};
|
|
6472
6338
|
}, [props.a11yRef]);
|
|
6473
|
-
return /* @__PURE__ */
|
|
6474
|
-
sheetOptions?.backing && /* @__PURE__ */
|
|
6475
|
-
(props.zones ?? []).map((zone) => /* @__PURE__ */
|
|
6339
|
+
return /* @__PURE__ */ jsxs5("group", { children: [
|
|
6340
|
+
sheetOptions?.backing && /* @__PURE__ */ jsx9(BackingSheet, { options: sheetOptions, count: total, removed }),
|
|
6341
|
+
(props.zones ?? []).map((zone) => /* @__PURE__ */ jsx9(DropZoneVisual, { registry: registry4, config: zone }, zone.id)),
|
|
6476
6342
|
slotConfigs.map((config, i) => {
|
|
6477
6343
|
const pose = poses[i];
|
|
6478
|
-
return /* @__PURE__ */
|
|
6344
|
+
return /* @__PURE__ */ jsx9(
|
|
6479
6345
|
"group",
|
|
6480
6346
|
{
|
|
6481
6347
|
ref: (g) => {
|
|
@@ -6490,7 +6356,7 @@ function InteractiveField(props) {
|
|
|
6490
6356
|
if (!hit) return;
|
|
6491
6357
|
pressRef.current = { slot: i, pointerId: e.pointerId, startX: hit[0], startY: hit[1] };
|
|
6492
6358
|
},
|
|
6493
|
-
children: /* @__PURE__ */
|
|
6359
|
+
children: /* @__PURE__ */ jsx9(
|
|
6494
6360
|
PaperMesh,
|
|
6495
6361
|
{
|
|
6496
6362
|
ref: (h) => {
|
|
@@ -6508,6 +6374,154 @@ function InteractiveField(props) {
|
|
|
6508
6374
|
] });
|
|
6509
6375
|
}
|
|
6510
6376
|
|
|
6377
|
+
// src/field/keyboardMirror.tsx
|
|
6378
|
+
import { useState as useState6 } from "react";
|
|
6379
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
6380
|
+
function fieldKeyboardStep(carry2, slot, key, controller) {
|
|
6381
|
+
if (!carry2) {
|
|
6382
|
+
if (key === "Enter" || key === " ") {
|
|
6383
|
+
return { carry: controller.pick(slot) ? { slot, zoneIndex: 0 } : null, handled: true };
|
|
6384
|
+
}
|
|
6385
|
+
return { carry: carry2, handled: false };
|
|
6386
|
+
}
|
|
6387
|
+
if (carry2.slot !== slot) return { carry: carry2, handled: false };
|
|
6388
|
+
const zoneCount = Math.max(controller.zoneIds().length, 1);
|
|
6389
|
+
if (key === "ArrowRight" || key === "ArrowDown") {
|
|
6390
|
+
return { carry: { ...carry2, zoneIndex: (carry2.zoneIndex + 1) % zoneCount }, handled: true };
|
|
6391
|
+
}
|
|
6392
|
+
if (key === "ArrowLeft" || key === "ArrowUp") {
|
|
6393
|
+
return {
|
|
6394
|
+
carry: { ...carry2, zoneIndex: (carry2.zoneIndex - 1 + zoneCount) % zoneCount },
|
|
6395
|
+
handled: true
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6398
|
+
if (key === "Enter" || key === " ") {
|
|
6399
|
+
const zone = controller.zoneIds()[carry2.zoneIndex];
|
|
6400
|
+
if (zone) controller.placeAtZone(slot, zone);
|
|
6401
|
+
return { carry: null, handled: true };
|
|
6402
|
+
}
|
|
6403
|
+
if (key === "Escape") {
|
|
6404
|
+
controller.cancel(slot);
|
|
6405
|
+
return { carry: null, handled: true };
|
|
6406
|
+
}
|
|
6407
|
+
return { carry: carry2, handled: false };
|
|
6408
|
+
}
|
|
6409
|
+
var mirrorHidden = {
|
|
6410
|
+
position: "absolute",
|
|
6411
|
+
width: 1,
|
|
6412
|
+
height: 1,
|
|
6413
|
+
padding: 0,
|
|
6414
|
+
margin: -1,
|
|
6415
|
+
overflow: "hidden",
|
|
6416
|
+
clip: "rect(0 0 0 0)",
|
|
6417
|
+
whiteSpace: "nowrap",
|
|
6418
|
+
border: 0
|
|
6419
|
+
};
|
|
6420
|
+
function FieldKeyboardMirror({
|
|
6421
|
+
papers,
|
|
6422
|
+
controller
|
|
6423
|
+
}) {
|
|
6424
|
+
const [carrying, setCarrying] = useState6(null);
|
|
6425
|
+
const paperLabel = (slot, i) => {
|
|
6426
|
+
try {
|
|
6427
|
+
const config = resolveConfig({ preset: slot.preset });
|
|
6428
|
+
const content = slot.content ? contentSchema.parse(slot.content) : config.content;
|
|
6429
|
+
return `Paper ${i + 1}: ${contentText({ ...config, content })}`;
|
|
6430
|
+
} catch {
|
|
6431
|
+
return `Paper ${i + 1}`;
|
|
6432
|
+
}
|
|
6433
|
+
};
|
|
6434
|
+
const onKeyDown = (i) => (e) => {
|
|
6435
|
+
const ctl = controller.current;
|
|
6436
|
+
if (!ctl) return;
|
|
6437
|
+
const { carry: carry2, handled } = fieldKeyboardStep(carrying, i, e.key, ctl);
|
|
6438
|
+
if (handled) e.preventDefault();
|
|
6439
|
+
if (carry2 !== carrying) setCarrying(carry2);
|
|
6440
|
+
};
|
|
6441
|
+
return /* @__PURE__ */ jsxs6("fieldset", { style: mirrorHidden, children: [
|
|
6442
|
+
/* @__PURE__ */ jsx10("legend", { children: "Interactive papers" }),
|
|
6443
|
+
papers.map((slot, i) => /* @__PURE__ */ jsxs6(
|
|
6444
|
+
"button",
|
|
6445
|
+
{
|
|
6446
|
+
type: "button",
|
|
6447
|
+
onKeyDown: onKeyDown(i),
|
|
6448
|
+
"aria-label": paperLabel(slot, i),
|
|
6449
|
+
"aria-pressed": carrying?.slot === i,
|
|
6450
|
+
children: [
|
|
6451
|
+
paperLabel(slot, i),
|
|
6452
|
+
carrying?.slot === i && /* @__PURE__ */ jsxs6("span", { "aria-live": "polite", children: [
|
|
6453
|
+
" ",
|
|
6454
|
+
"\u2014 carrying; zone ",
|
|
6455
|
+
controller.current?.zoneIds()[carrying.zoneIndex] ?? "none",
|
|
6456
|
+
"; Enter places, Escape returns"
|
|
6457
|
+
] })
|
|
6458
|
+
]
|
|
6459
|
+
},
|
|
6460
|
+
i
|
|
6461
|
+
))
|
|
6462
|
+
] });
|
|
6463
|
+
}
|
|
6464
|
+
|
|
6465
|
+
// src/field/framing.ts
|
|
6466
|
+
var PHASE_SAMPLES = 8;
|
|
6467
|
+
function resolveLayoutOptions(layoutId, layout, propOptions, firstSheet) {
|
|
6468
|
+
const parsed = layout.optionsSchema.parse({
|
|
6469
|
+
...layout.defaults,
|
|
6470
|
+
...propOptions
|
|
6471
|
+
});
|
|
6472
|
+
if (layoutId !== "sheet") return parsed;
|
|
6473
|
+
return withSheetCellFromPaper(
|
|
6474
|
+
parsed,
|
|
6475
|
+
propOptions,
|
|
6476
|
+
firstSheet
|
|
6477
|
+
);
|
|
6478
|
+
}
|
|
6479
|
+
function fieldBounds(layout, n, options, sheet2) {
|
|
6480
|
+
const reach = Math.hypot(sheet2.width, sheet2.height) / 2;
|
|
6481
|
+
if (n <= 0) return { center: [0, 0, 0], half: [reach, reach, 0] };
|
|
6482
|
+
const min = [Infinity, Infinity, Infinity];
|
|
6483
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
6484
|
+
for (let s = 0; s < PHASE_SAMPLES; s++) {
|
|
6485
|
+
for (let i = 0; i < n; i++) {
|
|
6486
|
+
const pose = layout.pose(i, n, options, s / PHASE_SAMPLES, sheet2);
|
|
6487
|
+
const r = reach * Math.max(pose.scale, 0);
|
|
6488
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
6489
|
+
min[axis] = Math.min(min[axis], pose.position[axis] - r);
|
|
6490
|
+
max[axis] = Math.max(max[axis], pose.position[axis] + r);
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
}
|
|
6494
|
+
return {
|
|
6495
|
+
center: [(min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2],
|
|
6496
|
+
half: [(max[0] - min[0]) / 2, (max[1] - min[1]) / 2, (max[2] - min[2]) / 2]
|
|
6497
|
+
};
|
|
6498
|
+
}
|
|
6499
|
+
var LIFT = 0.11;
|
|
6500
|
+
var DEG8 = Math.PI / 180;
|
|
6501
|
+
function fitCamera(layout, n, options, sheet2, fovDeg, aspect, margin = 1.06) {
|
|
6502
|
+
const { center } = fieldBounds(layout, n, options, sheet2);
|
|
6503
|
+
const reach = Math.hypot(sheet2.width, sheet2.height) / 2 * margin;
|
|
6504
|
+
const vTan = Math.tan(fovDeg * DEG8 / 2);
|
|
6505
|
+
const hTan = vTan * Math.max(aspect, 0.01);
|
|
6506
|
+
let distance = 0.1;
|
|
6507
|
+
for (let s = 0; s < PHASE_SAMPLES; s++) {
|
|
6508
|
+
for (let i = 0; i < Math.max(n, 0); i++) {
|
|
6509
|
+
const pose = layout.pose(i, n, options, s / PHASE_SAMPLES, sheet2);
|
|
6510
|
+
const r = reach * Math.max(pose.scale, 0);
|
|
6511
|
+
const depth = pose.position[2] - center[2];
|
|
6512
|
+
distance = Math.max(
|
|
6513
|
+
distance,
|
|
6514
|
+
(Math.abs(pose.position[0] - center[0]) + r) / hTan + depth,
|
|
6515
|
+
(Math.abs(pose.position[1] - center[1]) + r) / vTan + depth
|
|
6516
|
+
);
|
|
6517
|
+
}
|
|
6518
|
+
}
|
|
6519
|
+
return {
|
|
6520
|
+
position: [center[0], center[1] + distance * LIFT, center[2] + distance],
|
|
6521
|
+
target: center
|
|
6522
|
+
};
|
|
6523
|
+
}
|
|
6524
|
+
|
|
6511
6525
|
// src/PaperField.tsx
|
|
6512
6526
|
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
6513
6527
|
var PaperFieldMesh = forwardRef2(
|
|
@@ -6854,63 +6868,18 @@ export {
|
|
|
6854
6868
|
spanAlong,
|
|
6855
6869
|
segmentsForArc,
|
|
6856
6870
|
segmentsForSine,
|
|
6857
|
-
cornerNames,
|
|
6858
|
-
curlOptionsSchema,
|
|
6859
|
-
curl,
|
|
6860
|
-
peelOptionsSchema,
|
|
6861
|
-
peel,
|
|
6862
|
-
unrollOptionsSchema,
|
|
6863
|
-
unroll,
|
|
6864
|
-
flipOptionsSchema,
|
|
6865
|
-
flip,
|
|
6866
|
-
letterFoldOptionsSchema,
|
|
6867
|
-
letterFold,
|
|
6868
|
-
hangOptionsSchema,
|
|
6869
|
-
hang,
|
|
6870
|
-
flyOptionsSchema,
|
|
6871
|
-
fly,
|
|
6872
|
-
fallOptionsSchema,
|
|
6873
|
-
fall,
|
|
6874
|
-
carryOptionsSchema,
|
|
6875
|
-
carry,
|
|
6876
|
-
dampTo,
|
|
6877
|
-
gust,
|
|
6878
|
-
flightPose,
|
|
6879
|
-
carryDrive,
|
|
6880
|
-
flightOptionsSchema,
|
|
6881
|
-
flight,
|
|
6882
|
-
crumpleBehaviorOptionsSchema,
|
|
6883
|
-
crumpleBehavior,
|
|
6884
|
-
settleOptionsSchema,
|
|
6885
|
-
settle,
|
|
6886
|
-
ribbonOptionsSchema,
|
|
6887
|
-
ribbon,
|
|
6888
|
-
sheetSchema,
|
|
6889
6871
|
stockNames,
|
|
6890
|
-
stockSchema,
|
|
6891
|
-
backContentSchema,
|
|
6892
|
-
cardContentSchema,
|
|
6893
|
-
receiptContentSchema,
|
|
6894
|
-
contentSchema,
|
|
6895
6872
|
paperEdges,
|
|
6896
|
-
surfaceSchema,
|
|
6897
6873
|
behaviorConfigSchema,
|
|
6898
|
-
deformerInstanceSchema,
|
|
6899
6874
|
physicsNames,
|
|
6900
6875
|
clothConfigSchema,
|
|
6901
|
-
physicsSchema,
|
|
6902
6876
|
lightingNames,
|
|
6903
|
-
filmNames,
|
|
6904
|
-
sceneSchema,
|
|
6905
6877
|
coreStateNames,
|
|
6906
|
-
stateTransitionSchema,
|
|
6907
6878
|
stateDefSchema,
|
|
6908
6879
|
paperStatesSchema,
|
|
6909
6880
|
paperConfigSchema,
|
|
6910
6881
|
parsePreset,
|
|
6911
6882
|
serializePreset,
|
|
6912
|
-
resolveSegments,
|
|
6913
|
-
createSheetGeometry,
|
|
6914
6883
|
stocks,
|
|
6915
6884
|
getStock,
|
|
6916
6885
|
getPreset,
|
|
@@ -6919,111 +6888,40 @@ export {
|
|
|
6919
6888
|
isBuiltinPreset,
|
|
6920
6889
|
listPresets,
|
|
6921
6890
|
uniquePresetName,
|
|
6922
|
-
receiptTotals,
|
|
6923
|
-
barcodeBars,
|
|
6924
6891
|
wrapLines,
|
|
6925
|
-
rollOptionsSchema,
|
|
6926
|
-
roll,
|
|
6927
|
-
bendOptionsSchema,
|
|
6928
|
-
bend,
|
|
6929
|
-
foldOptionsSchema,
|
|
6930
|
-
fold,
|
|
6931
|
-
waveOptionsSchema,
|
|
6932
|
-
wave,
|
|
6933
|
-
drapeOptionsSchema,
|
|
6934
|
-
drape,
|
|
6935
|
-
crumpleOptionsSchema,
|
|
6936
|
-
crumple,
|
|
6937
6892
|
registerDeformer,
|
|
6938
6893
|
getDeformer,
|
|
6939
6894
|
listDeformers,
|
|
6940
|
-
resolveDeformerStack,
|
|
6941
|
-
applyDeformerStack,
|
|
6942
6895
|
displacePoint,
|
|
6943
|
-
stackMinSegments,
|
|
6944
|
-
stackAutoSegments,
|
|
6945
6896
|
registerBehavior,
|
|
6946
6897
|
getBehavior,
|
|
6947
6898
|
listBehaviors,
|
|
6948
6899
|
idleNames,
|
|
6949
|
-
idlePresets,
|
|
6950
|
-
getIdlePreset,
|
|
6951
|
-
ClothSim,
|
|
6952
|
-
lightingPresets,
|
|
6953
|
-
getLightingPreset,
|
|
6954
6900
|
lightSchema,
|
|
6955
6901
|
lightAngles,
|
|
6956
|
-
lightPosition,
|
|
6957
6902
|
resolveLighting,
|
|
6958
|
-
TRANSMISSION_GAIN,
|
|
6959
|
-
translucencyValues,
|
|
6960
|
-
translucencyUniforms,
|
|
6961
|
-
composeSurface,
|
|
6962
6903
|
LightRig,
|
|
6963
|
-
PaperMaterial,
|
|
6964
6904
|
usePrefersReducedMotion,
|
|
6965
6905
|
supportsWebGL,
|
|
6966
|
-
contentText,
|
|
6967
6906
|
PaperMirror,
|
|
6968
6907
|
PaperFallback,
|
|
6969
|
-
ON_TWOS_FPS,
|
|
6970
|
-
quantizeTime,
|
|
6971
|
-
quantizeProgress,
|
|
6972
|
-
stateEventTransitions,
|
|
6973
|
-
stripStates,
|
|
6974
6908
|
resolveStateConfig,
|
|
6975
6909
|
recordStateOverride,
|
|
6976
|
-
flattenNumeric,
|
|
6977
|
-
PaperStateMachine,
|
|
6978
6910
|
usePaperStates,
|
|
6979
6911
|
useResolvedConfig,
|
|
6980
6912
|
resolveConfig,
|
|
6981
6913
|
PaperMesh,
|
|
6982
6914
|
cssColorOr,
|
|
6983
|
-
makeGoboTexture,
|
|
6984
6915
|
PaperLighting,
|
|
6985
|
-
DropZoneRegistry,
|
|
6986
|
-
zoneAccepts,
|
|
6987
6916
|
DropZone,
|
|
6988
6917
|
sheetLayoutSchema,
|
|
6989
|
-
SHEET_LIFT,
|
|
6990
|
-
withSheetCellFromPaper,
|
|
6991
|
-
sheetSlotXY,
|
|
6992
|
-
sheetBackingSize,
|
|
6993
|
-
outwardCorner,
|
|
6994
|
-
tornEdgesOnDetach,
|
|
6995
|
-
groupFieldPapers,
|
|
6996
|
-
resolveFieldSlotConfig,
|
|
6997
|
-
atlasGrid,
|
|
6998
|
-
useContentAtlas,
|
|
6999
|
-
stackUniformValues,
|
|
7000
6918
|
buildDisplacementGLSL,
|
|
7001
|
-
buildFieldVertexShader,
|
|
7002
|
-
buildFieldFragmentShader,
|
|
7003
6919
|
walkPathSchema,
|
|
7004
6920
|
createWalkPath,
|
|
7005
6921
|
getWalkPath,
|
|
7006
|
-
ring,
|
|
7007
|
-
fan,
|
|
7008
|
-
spread,
|
|
7009
|
-
pile,
|
|
7010
|
-
wall,
|
|
7011
|
-
spill,
|
|
7012
|
-
sweep,
|
|
7013
|
-
book,
|
|
7014
|
-
accordion,
|
|
7015
|
-
rack,
|
|
7016
|
-
colonnade,
|
|
7017
|
-
sheet,
|
|
7018
6922
|
registerLayout,
|
|
7019
6923
|
getLayout,
|
|
7020
6924
|
listLayouts,
|
|
7021
|
-
silhouetteRects,
|
|
7022
|
-
lightenHex,
|
|
7023
|
-
drawBacking,
|
|
7024
|
-
fieldKeyboardStep,
|
|
7025
|
-
fieldBounds,
|
|
7026
|
-
fitCamera,
|
|
7027
6925
|
PaperFieldMesh,
|
|
7028
6926
|
PaperField,
|
|
7029
6927
|
diffConfig,
|
|
@@ -7032,4 +6930,4 @@ export {
|
|
|
7032
6930
|
describeConfig,
|
|
7033
6931
|
buildAgentPayload
|
|
7034
6932
|
};
|
|
7035
|
-
//# sourceMappingURL=chunk-
|
|
6933
|
+
//# sourceMappingURL=chunk-4ZU5DZEF.js.map
|