paperlab 0.3.0 → 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/README.md +154 -45
- package/dist/{chunk-3IMUEESH.js → chunk-4ZU5DZEF.js} +1705 -1807
- package/dist/chunk-4ZU5DZEF.js.map +1 -0
- package/dist/index.cjs +493 -748
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +419 -1874
- package/dist/index.d.ts +419 -1874
- package/dist/index.js +183 -431
- package/dist/index.js.map +1 -1
- package/dist/{slots-D0Nc_5dq.d.cts → slots-BYoLxVZ4.d.cts} +3 -656
- package/dist/{slots-D0Nc_5dq.d.ts → slots-BYoLxVZ4.d.ts} +3 -656
- package/dist/stage.cjs +3 -17
- 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-3IMUEESH.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),
|
|
@@ -1150,7 +1156,7 @@ var paperConfigSchema = z14.object({
|
|
|
1150
1156
|
physics: physicsSchema.default("none"),
|
|
1151
1157
|
scene: sceneSchema.default({}),
|
|
1152
1158
|
onTwos: z14.boolean().default(false),
|
|
1153
|
-
/** Interaction state machine — overrides-on-base diffs
|
|
1159
|
+
/** Interaction state machine — overrides-on-base diffs. */
|
|
1154
1160
|
states: paperStatesSchema.optional()
|
|
1155
1161
|
}).superRefine((config, ctx) => {
|
|
1156
1162
|
if (typeof config.physics === "object" && (config.behavior || config.deformers)) {
|
|
@@ -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: {
|
|
@@ -1438,7 +1420,7 @@ var builtins = {
|
|
|
1438
1420
|
meta: { name: "Blank sheet", tags: ["starter"] },
|
|
1439
1421
|
stock: "printer"
|
|
1440
1422
|
},
|
|
1441
|
-
// The
|
|
1423
|
+
// The driving use case: one stamp of the 2×5 block. Hover peels the
|
|
1442
1424
|
// outward-facing corner ('auto' resolves per sheet slot), pressing deepens
|
|
1443
1425
|
// the peel; the perforation tears when it detaches (field auto-wiring).
|
|
1444
1426
|
"postage-stamp": {
|
|
@@ -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,622 +2510,272 @@ 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 };
|
|
3229
|
-
}
|
|
3230
|
-
if (deckle) {
|
|
3231
|
-
chunks.push(DECKLE_CHUNK);
|
|
3232
|
-
calls.push("plDeckle(csm_DiffuseColor);");
|
|
3233
|
-
uniforms.uDeckleEdges = { value: edgeFlags(deckle.edges) };
|
|
3234
|
-
uniforms.uDeckleRoughness = { value: deckle.roughness };
|
|
3235
|
-
}
|
|
3236
|
-
if (perforation) {
|
|
3237
|
-
const edges = perforation.edges === "all" ? [...paperEdges] : perforation.edges;
|
|
3238
|
-
chunks.push(PERFORATION_CHUNK);
|
|
3239
|
-
calls.push("plPerforation(csm_DiffuseColor);");
|
|
3240
|
-
uniforms.uPerfEdges = { value: edgeFlags(edges) };
|
|
3241
|
-
uniforms.uPerfTorn = {
|
|
3242
|
-
value: new THREE4.Vector4(
|
|
3243
|
-
...paperEdges.map((e) => edges.includes(e) && perforation.state[e] === "torn" ? 1 : 0)
|
|
3244
|
-
)
|
|
3245
|
-
};
|
|
3246
|
-
uniforms.uPerfRadius = { value: perforation.holeRadius };
|
|
3247
|
-
uniforms.uPerfSpacing = { value: perforation.spacing };
|
|
3248
|
-
uniforms.uSheetSize = { value: new THREE4.Vector2(sheet2.width, sheet2.height) };
|
|
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;
|
|
3249
2738
|
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
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;
|
|
3257
2758
|
}
|
|
3258
|
-
if (
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
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
|
+
}
|
|
3262
2764
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
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);
|
|
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;
|
|
3283
2774
|
}
|
|
3284
|
-
|
|
3285
|
-
if (!gl_FrontFacing) csm_DiffuseColor.rgb *= uBackDarken;
|
|
3286
|
-
${stock.adhesive ? "// Adhesive underside: higher specular than the printed face.\n if (!gl_FrontFacing) csm_Roughness = 0.18;" : ""}
|
|
3287
|
-
// What the key light pushes through the sheet, filtered by the ink on it.
|
|
3288
|
-
csm_Emissive = plTransmission(front);
|
|
2775
|
+
node[keys[keys.length - 1]] = value;
|
|
3289
2776
|
}
|
|
3290
|
-
|
|
3291
|
-
);
|
|
3292
|
-
return {
|
|
3293
|
-
structureKey: `${[
|
|
3294
|
-
grain !== void 0 || banding > 0 ? "g" : "",
|
|
3295
|
-
deckle ? "d" : "",
|
|
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
|
|
3305
|
-
};
|
|
3306
|
-
}
|
|
3307
|
-
function padPositions(positions) {
|
|
3308
|
-
const out = positions.slice(0, 4);
|
|
3309
|
-
while (out.length < 4) out.push(-1);
|
|
3310
|
-
return out;
|
|
3311
|
-
}
|
|
3312
|
-
|
|
3313
|
-
// src/scene/rig.tsx
|
|
3314
|
-
import { createContext, useContext } from "react";
|
|
3315
|
-
import { jsx } from "react/jsx-runtime";
|
|
3316
|
-
var LightRigContext = createContext(null);
|
|
3317
|
-
function LightRig({ rig, children }) {
|
|
3318
|
-
return /* @__PURE__ */ jsx(LightRigContext.Provider, { value: rig, children });
|
|
3319
|
-
}
|
|
3320
|
-
function useLightRig(own) {
|
|
3321
|
-
return useContext(LightRigContext) ?? getLightingPreset(own);
|
|
3322
|
-
}
|
|
3323
|
-
|
|
3324
|
-
// src/surface/PaperMaterial.tsx
|
|
3325
|
-
import * as THREE5 from "three";
|
|
3326
|
-
import { useEffect, useMemo } from "react";
|
|
3327
|
-
import CustomShaderMaterial from "three-custom-shader-material";
|
|
3328
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
3329
|
-
function PaperMaterial({
|
|
3330
|
-
stock,
|
|
3331
|
-
texture,
|
|
3332
|
-
backTexture,
|
|
3333
|
-
surface,
|
|
3334
|
-
thickness,
|
|
3335
|
-
sheet: sheet2,
|
|
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
|
-
);
|
|
3383
|
-
}
|
|
3384
|
-
|
|
3385
|
-
// src/a11y/index.tsx
|
|
3386
|
-
import { useEffect as useEffect2, useState } from "react";
|
|
3387
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
3388
|
-
function usePrefersReducedMotion(override) {
|
|
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;
|
|
3400
|
-
}
|
|
3401
|
-
var webglSupport = null;
|
|
3402
|
-
function supportsWebGL() {
|
|
3403
|
-
if (webglSupport !== null) return webglSupport;
|
|
3404
|
-
try {
|
|
3405
|
-
const canvas = document.createElement("canvas");
|
|
3406
|
-
webglSupport = Boolean(canvas.getContext("webgl2") ?? canvas.getContext("webgl"));
|
|
3407
|
-
} catch {
|
|
3408
|
-
webglSupport = false;
|
|
3409
|
-
}
|
|
3410
|
-
return webglSupport;
|
|
3411
|
-
}
|
|
3412
|
-
function contentText(config) {
|
|
3413
|
-
const content = config.content;
|
|
3414
|
-
if (content.type === "text") return content.text;
|
|
3415
|
-
if (content.type === "image") return content.alt ?? "An image printed on paper.";
|
|
3416
|
-
if (content.type === "receipt") {
|
|
3417
|
-
const totals = receiptTotals(content);
|
|
3418
|
-
const items = content.items.map((i) => `${i.name} ${i.price.toFixed(2)}`).join(", ");
|
|
3419
|
-
return `Receipt from ${content.store}: ${items}. Total ${totals.total.toFixed(2)}. ${content.footer}`;
|
|
3420
|
-
}
|
|
3421
|
-
return "A blank sheet of paper.";
|
|
3422
|
-
}
|
|
3423
|
-
var visuallyHidden = {
|
|
3424
|
-
position: "absolute",
|
|
3425
|
-
width: 1,
|
|
3426
|
-
height: 1,
|
|
3427
|
-
padding: 0,
|
|
3428
|
-
margin: -1,
|
|
3429
|
-
overflow: "hidden",
|
|
3430
|
-
clip: "rect(0 0 0 0)",
|
|
3431
|
-
whiteSpace: "nowrap",
|
|
3432
|
-
border: 0
|
|
3433
|
-
};
|
|
3434
|
-
function PaperMirror({ config }) {
|
|
3435
|
-
return /* @__PURE__ */ jsx3("div", { style: visuallyHidden, "aria-hidden": false, children: contentText(config) });
|
|
3436
|
-
}
|
|
3437
|
-
function PaperFallback({ config }) {
|
|
3438
|
-
const stock = getStock(config.stock);
|
|
3439
|
-
const isImage = config.content.type === "image";
|
|
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
|
-
);
|
|
3480
|
-
}
|
|
3481
|
-
|
|
3482
|
-
// src/motion/onTwos.ts
|
|
3483
|
-
var ON_TWOS_FPS = 12;
|
|
3484
|
-
function quantizeTime(t, fps = ON_TWOS_FPS) {
|
|
3485
|
-
return Math.floor(t * fps) / fps;
|
|
3486
|
-
}
|
|
3487
|
-
function quantizeProgress(p, duration, fps = ON_TWOS_FPS) {
|
|
3488
|
-
const steps = Math.max(1, Math.round(duration * fps));
|
|
3489
|
-
return Math.round(p * steps) / steps;
|
|
3490
|
-
}
|
|
3491
|
-
|
|
3492
|
-
// src/states/machine.ts
|
|
3493
|
-
import { gsap } from "gsap";
|
|
3494
|
-
var stateEventTransitions = {
|
|
3495
|
-
rest: { enter: "hover" },
|
|
3496
|
-
hover: { leave: "rest", down: "pressed" },
|
|
3497
|
-
pressed: { up: "hover", pick: "picked" },
|
|
3498
|
-
picked: { place: "placed", return: "rest" },
|
|
3499
|
-
placed: {}
|
|
3500
|
-
};
|
|
3501
|
-
var DEFAULT_TRANSITION = { duration: 0.35, ease: "power2.out" };
|
|
3502
|
-
function stripStates(config) {
|
|
3503
|
-
if (!config.states) return config;
|
|
3504
|
-
const { states: _states, ...rest } = config;
|
|
3505
|
-
return rest;
|
|
3506
|
-
}
|
|
3507
|
-
function resolveStateConfig(base, state) {
|
|
3508
|
-
const def = base.states?.states[state];
|
|
3509
|
-
const flat = stripStates(base);
|
|
3510
|
-
if (!def || Object.keys(def.overrides).length === 0) return flat;
|
|
3511
|
-
return paperConfigSchema.parse(mergeConfig(flat, def.overrides));
|
|
3512
|
-
}
|
|
3513
|
-
function pruneUndefined(value) {
|
|
3514
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
3515
|
-
const out = {};
|
|
3516
|
-
for (const [key, v] of Object.entries(value)) {
|
|
3517
|
-
if (v === void 0) continue;
|
|
3518
|
-
const pruned = pruneUndefined(v);
|
|
3519
|
-
const isEmptyObject = pruned !== null && typeof pruned === "object" && !Array.isArray(pruned) && Object.keys(pruned).length === 0;
|
|
3520
|
-
if (!isEmptyObject) out[key] = pruned;
|
|
3521
|
-
}
|
|
3522
|
-
return out;
|
|
3523
|
-
}
|
|
3524
|
-
function recordStateOverride(config, stateName, patch) {
|
|
3525
|
-
const cleaned = pruneUndefined(patch);
|
|
3526
|
-
const states = config.states ?? paperStatesSchema.parse({});
|
|
3527
|
-
const def = states.states[stateName] ?? stateDefSchema.parse({});
|
|
3528
|
-
const overrides = mergeConfig(def.overrides, cleaned);
|
|
3529
|
-
return paperConfigSchema.parse({
|
|
3530
|
-
...config,
|
|
3531
|
-
states: {
|
|
3532
|
-
...states,
|
|
3533
|
-
states: { ...states.states, [stateName]: { ...def, overrides } }
|
|
3534
|
-
}
|
|
3535
|
-
});
|
|
3536
|
-
}
|
|
3537
|
-
function flattenNumeric(value, prefix = "", out = {}) {
|
|
3538
|
-
if (typeof value === "number") {
|
|
3539
|
-
if (prefix) out[prefix] = value;
|
|
3540
|
-
return out;
|
|
3541
|
-
}
|
|
3542
|
-
if (value !== null && typeof value === "object") {
|
|
3543
|
-
const entries = Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value);
|
|
3544
|
-
for (const [key, v] of entries) {
|
|
3545
|
-
flattenNumeric(v, prefix ? `${prefix}.${key}` : key, out);
|
|
3546
|
-
}
|
|
3547
|
-
}
|
|
3548
|
-
return out;
|
|
3549
|
-
}
|
|
3550
|
-
function setPath(target, path, value) {
|
|
3551
|
-
const keys = path.split(".");
|
|
3552
|
-
let node = target;
|
|
3553
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
3554
|
-
const next = node[keys[i]];
|
|
3555
|
-
if (next === null || typeof next !== "object") return;
|
|
3556
|
-
node = next;
|
|
3557
|
-
}
|
|
3558
|
-
node[keys[keys.length - 1]] = value;
|
|
3559
|
-
}
|
|
3560
|
-
function applyFlat(target, flat) {
|
|
3561
|
-
for (const path in flat) setPath(target, path, flat[path]);
|
|
2777
|
+
function applyFlat(target, flat) {
|
|
2778
|
+
for (const path in flat) setPath(target, path, flat[path]);
|
|
3562
2779
|
}
|
|
3563
2780
|
var clone = (v) => JSON.parse(JSON.stringify(v));
|
|
3564
2781
|
var PaperStateMachine = class {
|
|
@@ -3736,10 +2953,10 @@ var PaperStateMachine = class {
|
|
|
3736
2953
|
};
|
|
3737
2954
|
|
|
3738
2955
|
// src/states/usePaperStates.ts
|
|
3739
|
-
import { useEffect as
|
|
2956
|
+
import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
3740
2957
|
function usePaperStates(config, enabled, instant, onAction, onStateChange) {
|
|
3741
2958
|
const live = enabled && Boolean(config.states);
|
|
3742
|
-
const key =
|
|
2959
|
+
const key = useMemo(() => JSON.stringify(config), [config]);
|
|
3743
2960
|
const onActionRef = useRef(onAction);
|
|
3744
2961
|
onActionRef.current = onAction;
|
|
3745
2962
|
const onStateChangeRef = useRef(onStateChange);
|
|
@@ -3747,7 +2964,7 @@ function usePaperStates(config, enabled, instant, onAction, onStateChange) {
|
|
|
3747
2964
|
const machineRef = useRef(null);
|
|
3748
2965
|
const lastStateRef = useRef("rest");
|
|
3749
2966
|
const [animated, setAnimated] = useState2(null);
|
|
3750
|
-
|
|
2967
|
+
useEffect2(() => {
|
|
3751
2968
|
if (!live) {
|
|
3752
2969
|
machineRef.current?.dispose();
|
|
3753
2970
|
machineRef.current = null;
|
|
@@ -3774,7 +2991,7 @@ function usePaperStates(config, enabled, instant, onAction, onStateChange) {
|
|
|
3774
2991
|
lastStateRef.current = machine.state;
|
|
3775
2992
|
setAnimated({ config: machine.structuralConfig(), state: machine.state });
|
|
3776
2993
|
}, [key, live, instant]);
|
|
3777
|
-
|
|
2994
|
+
useEffect2(
|
|
3778
2995
|
() => () => {
|
|
3779
2996
|
machineRef.current?.dispose();
|
|
3780
2997
|
machineRef.current = null;
|
|
@@ -3794,13 +3011,64 @@ import { gsap as gsap2 } from "gsap";
|
|
|
3794
3011
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
3795
3012
|
import { forwardRef, useEffect as useEffect5, useImperativeHandle, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
3796
3013
|
|
|
3797
|
-
// src/core/
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
const
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
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;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
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;
|
|
3071
|
+
}
|
|
3804
3072
|
function deepEqual(a, b) {
|
|
3805
3073
|
if (Object.is(a, b)) return true;
|
|
3806
3074
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
@@ -3818,9 +3086,33 @@ function deepEqual(a, b) {
|
|
|
3818
3086
|
return true;
|
|
3819
3087
|
}
|
|
3820
3088
|
|
|
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)];
|
|
3101
|
+
}
|
|
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)];
|
|
3107
|
+
}
|
|
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
|
+
|
|
3821
3113
|
// src/content/texture.ts
|
|
3822
|
-
import * as
|
|
3823
|
-
import { useEffect as
|
|
3114
|
+
import * as THREE2 from "three";
|
|
3115
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
3824
3116
|
|
|
3825
3117
|
// src/content/card.ts
|
|
3826
3118
|
var TITLE_RATIO = 0.52;
|
|
@@ -3955,8 +3247,8 @@ function renderContentToCanvas(content, sheet2, stock, image) {
|
|
|
3955
3247
|
return canvas;
|
|
3956
3248
|
}
|
|
3957
3249
|
function makeTexture(canvas) {
|
|
3958
|
-
const tex = new
|
|
3959
|
-
tex.colorSpace =
|
|
3250
|
+
const tex = new THREE2.CanvasTexture(canvas);
|
|
3251
|
+
tex.colorSpace = THREE2.SRGBColorSpace;
|
|
3960
3252
|
tex.anisotropy = 8;
|
|
3961
3253
|
tex.generateMipmaps = true;
|
|
3962
3254
|
return tex;
|
|
@@ -3964,7 +3256,7 @@ function makeTexture(canvas) {
|
|
|
3964
3256
|
function useContentTexture(content, sheet2, stock) {
|
|
3965
3257
|
const [texture, setTexture] = useState3(null);
|
|
3966
3258
|
const key = JSON.stringify({ content: content ?? null, w: sheet2.width, h: sheet2.height, stock: stock.id });
|
|
3967
|
-
|
|
3259
|
+
useEffect3(() => {
|
|
3968
3260
|
let disposed = false;
|
|
3969
3261
|
let tex = null;
|
|
3970
3262
|
if (!content) {
|
|
@@ -3999,129 +3291,849 @@ function useContentTexture(content, sheet2, stock) {
|
|
|
3999
3291
|
return texture;
|
|
4000
3292
|
}
|
|
4001
3293
|
|
|
4002
|
-
// src/
|
|
4003
|
-
import
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
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;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
position.needsUpdate = true;
|
|
3329
|
+
computeSheetNormals(geometry);
|
|
4017
3330
|
}
|
|
4018
|
-
function
|
|
4019
|
-
|
|
4020
|
-
|
|
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);
|
|
3336
|
+
}
|
|
3337
|
+
return point;
|
|
4021
3338
|
}
|
|
4022
|
-
function
|
|
4023
|
-
const
|
|
4024
|
-
const
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
if (props.scene) overrides.scene = { ...base.scene, ...props.scene };
|
|
4032
|
-
if (props.physics) overrides.physics = props.physics;
|
|
4033
|
-
if (props.onTwos !== void 0) overrides.onTwos = props.onTwos;
|
|
4034
|
-
return paperConfigSchema.parse(mergeConfig(base, overrides));
|
|
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);
|
|
3346
|
+
}
|
|
3347
|
+
return out;
|
|
4035
3348
|
}
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
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);
|
|
3358
|
+
}
|
|
3359
|
+
return out;
|
|
3360
|
+
}
|
|
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;
|
|
3367
|
+
}
|
|
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);
|
|
3437
|
+
}
|
|
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();
|
|
3448
|
+
}
|
|
3449
|
+
wake() {
|
|
3450
|
+
this.asleep = false;
|
|
3451
|
+
this.stillFrames = 0;
|
|
3452
|
+
}
|
|
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
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
this.grabbedIndex = best;
|
|
3468
|
+
this.wake();
|
|
3469
|
+
return best;
|
|
3470
|
+
}
|
|
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();
|
|
3481
|
+
}
|
|
3482
|
+
release() {
|
|
3483
|
+
this.grabbedIndex = -1;
|
|
3484
|
+
}
|
|
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;
|
|
3491
|
+
}
|
|
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;
|
|
3512
|
+
}
|
|
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);
|
|
3529
|
+
}
|
|
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;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
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
|
+
}
|
|
3561
|
+
}
|
|
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
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
};
|
|
3571
|
+
|
|
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
|
+
`
|
|
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);
|
|
3644
|
+
return {
|
|
3645
|
+
uTranslucency: { value: values.translucency },
|
|
3646
|
+
uBackLightDir: { value: values.direction },
|
|
3647
|
+
uBackLightColor: { value: values.color },
|
|
3648
|
+
uAmbientTransmission: { value: values.ambient }
|
|
3649
|
+
};
|
|
3650
|
+
}
|
|
3651
|
+
|
|
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;
|
|
3669
|
+
|
|
3670
|
+
float plHash(vec2 p) {
|
|
3671
|
+
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
|
|
3672
|
+
}
|
|
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;
|
|
3692
|
+
}
|
|
3693
|
+
return v;
|
|
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;
|
|
3708
|
+
|
|
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;
|
|
3728
|
+
|
|
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));
|
|
3744
|
+
}
|
|
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));
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
color.rgb = mix(color.rgb, vec3(1.0), fiber * 0.4);
|
|
3812
|
+
}
|
|
3813
|
+
`
|
|
3814
|
+
);
|
|
3815
|
+
var AGING_CHUNK = (
|
|
3816
|
+
/* glsl */
|
|
3817
|
+
`
|
|
3818
|
+
uniform float uAgingAmount;
|
|
3819
|
+
|
|
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);
|
|
3829
|
+
}
|
|
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 };
|
|
3862
|
+
}
|
|
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);
|
|
3922
|
+
}
|
|
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
|
+
};
|
|
3939
|
+
}
|
|
3940
|
+
function padPositions(positions) {
|
|
3941
|
+
const out = positions.slice(0, 4);
|
|
3942
|
+
while (out.length < 4) out.push(-1);
|
|
3943
|
+
return out;
|
|
3944
|
+
}
|
|
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]);
|
|
3970
|
+
useEffect4(() => {
|
|
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
|
+
}
|
|
3979
|
+
}
|
|
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;
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
// src/PaperMesh.tsx
|
|
4015
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
4016
|
+
function configInputs(props) {
|
|
4017
|
+
return [
|
|
4018
|
+
props.preset ?? null,
|
|
4019
|
+
props.sheet ?? null,
|
|
4020
|
+
props.stock ?? null,
|
|
4021
|
+
props.content ?? null,
|
|
4022
|
+
props.behavior ?? null,
|
|
4023
|
+
props.deformers ?? null,
|
|
4024
|
+
props.surface ?? null,
|
|
4025
|
+
props.scene ?? null,
|
|
4026
|
+
props.physics ?? null,
|
|
4027
|
+
props.onTwos ?? null
|
|
4028
|
+
];
|
|
4029
|
+
}
|
|
4030
|
+
function useResolvedConfig(props) {
|
|
4031
|
+
const inputs = useStable(configInputs(props));
|
|
4032
|
+
return useMemo3(() => resolveConfig(props), [inputs]);
|
|
4033
|
+
}
|
|
4034
|
+
function resolveConfig(props) {
|
|
4035
|
+
const base = props.preset ? typeof props.preset === "string" ? getPreset(props.preset) : parsePreset(props.preset) : paperConfigSchema.parse({});
|
|
4036
|
+
const overrides = {};
|
|
4037
|
+
if (props.sheet) overrides.sheet = { ...base.sheet, ...props.sheet };
|
|
4038
|
+
if (props.stock) overrides.stock = props.stock;
|
|
4039
|
+
if (props.content) overrides.content = props.content;
|
|
4040
|
+
if (props.behavior) overrides.behavior = props.behavior;
|
|
4041
|
+
if (props.deformers) overrides.deformers = props.deformers;
|
|
4042
|
+
if (props.surface) overrides.surface = { ...base.surface, ...props.surface };
|
|
4043
|
+
if (props.scene) overrides.scene = { ...base.scene, ...props.scene };
|
|
4044
|
+
if (props.physics) overrides.physics = props.physics;
|
|
4045
|
+
if (props.onTwos !== void 0) overrides.onTwos = props.onTwos;
|
|
4046
|
+
return paperConfigSchema.parse(mergeConfig(base, overrides));
|
|
4047
|
+
}
|
|
4048
|
+
var CLOTH_MAX_SEGMENTS = 28;
|
|
4049
|
+
var PROGRESS_SAMPLES = [0, 0.25, 0.5, 0.75, 1];
|
|
4050
|
+
var dragPlane = new THREE7.Plane();
|
|
4051
|
+
var dragPoint = new THREE7.Vector3();
|
|
4052
|
+
var planeNormal = new THREE7.Vector3();
|
|
4053
|
+
var anchorScratch = new THREE7.Vector3();
|
|
4054
|
+
var worldScratch = new THREE7.Vector3();
|
|
4055
|
+
var quatScratch = new THREE7.Quaternion();
|
|
4056
|
+
var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
4057
|
+
const resolved = useResolvedConfig(props);
|
|
4058
|
+
const reduced = usePrefersReducedMotion(props.reducedMotion);
|
|
4059
|
+
const statesLive = Boolean(resolved.states) && props.stateTriggers !== false;
|
|
4060
|
+
const {
|
|
4061
|
+
config,
|
|
4062
|
+
machine,
|
|
4063
|
+
state: machineState
|
|
4064
|
+
} = usePaperStates(resolved, statesLive, reduced, props.onStateAction, props.onStateChange);
|
|
4065
|
+
const behavior = config.behavior ? getBehavior(config.behavior.type) : null;
|
|
4066
|
+
const machineRef = useRef3(null);
|
|
4067
|
+
machineRef.current = machine;
|
|
4068
|
+
const resolvedRef = useRef3(resolved);
|
|
4069
|
+
resolvedRef.current = resolved;
|
|
4070
|
+
const isCloth = !reduced && typeof config.physics === "object";
|
|
4071
|
+
const idle = !reduced && typeof config.physics === "string" && config.physics !== "none" ? getIdlePreset(config.physics) : null;
|
|
4072
|
+
const meshRef = useRef3(null);
|
|
4073
|
+
const groupRef = useRef3(null);
|
|
4074
|
+
const handleRefs = useRef3([]);
|
|
4075
|
+
const overridesRef = useRef3({});
|
|
4076
|
+
const dirtyRef = useRef3(true);
|
|
4077
|
+
const playingRef = useRef3(false);
|
|
4078
|
+
const tweenRef = useRef3(null);
|
|
4079
|
+
const draggingRef = useRef3(null);
|
|
4080
|
+
const configRef = useRef3(config);
|
|
4081
|
+
configRef.current = config;
|
|
4082
|
+
const controls = useThree((s) => s.controls);
|
|
4083
|
+
const camera = useThree((s) => s.camera);
|
|
4084
|
+
const behaviorKey = JSON.stringify(config.behavior ?? null);
|
|
4085
|
+
const deformersKey = JSON.stringify(config.deformers ?? null);
|
|
4086
|
+
const sheetKey = JSON.stringify(config.sheet);
|
|
4087
|
+
const physicsKey = JSON.stringify(config.physics);
|
|
4088
|
+
useEffect5(() => {
|
|
4089
|
+
if (!draggingRef.current && !playingRef.current) overridesRef.current = {};
|
|
4090
|
+
dirtyRef.current = true;
|
|
4091
|
+
}, [behaviorKey, deformersKey, sheetKey, physicsKey]);
|
|
4092
|
+
const { minSegments, autoSegments, animatedStack } = useMemo3(() => {
|
|
4093
|
+
const cfg = configRef.current;
|
|
4094
|
+
const probe2 = buildStack(cfg, {});
|
|
4095
|
+
if (!probe2) {
|
|
4096
|
+
return {
|
|
4097
|
+
minSegments: [2, 2],
|
|
4098
|
+
autoSegments: [FLAT_SEGMENTS, FLAT_SEGMENTS],
|
|
4099
|
+
animatedStack: false
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
const want = stackAutoSegments(probe2, cfg.sheet);
|
|
4103
|
+
let animated = stackIsAnimated(probe2);
|
|
4104
|
+
if (cfg.behavior && !cfg.deformers) {
|
|
4105
|
+
const param = getBehavior(cfg.behavior.type).progressParam;
|
|
4106
|
+
for (const p of PROGRESS_SAMPLES) {
|
|
4107
|
+
const at = buildStack(cfg, { [param]: p });
|
|
4108
|
+
if (!at) continue;
|
|
4109
|
+
const [x, y] = stackAutoSegments(at, cfg.sheet);
|
|
4110
|
+
if (x > want[0]) want[0] = x;
|
|
4111
|
+
if (y > want[1]) want[1] = y;
|
|
4112
|
+
animated ||= stackIsAnimated(at);
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
return {
|
|
4116
|
+
minSegments: stackMinSegments(probe2, cfg.sheet),
|
|
4117
|
+
autoSegments: want,
|
|
4118
|
+
animatedStack: animated
|
|
4119
|
+
};
|
|
4120
|
+
}, [behaviorKey, deformersKey, physicsKey]);
|
|
4121
|
+
const geometry = useMemo3(() => {
|
|
4122
|
+
if (!isCloth) return createSheetGeometry(config.sheet, minSegments, autoSegments);
|
|
4123
|
+
const [sx, sy] = resolveSegments(config.sheet, 2);
|
|
4124
|
+
const capped = Math.min(Math.max(sx, sy), CLOTH_MAX_SEGMENTS);
|
|
4125
|
+
return new THREE7.PlaneGeometry(config.sheet.width, config.sheet.height, capped, capped);
|
|
4126
|
+
}, [sheetKey, minSegments, autoSegments, isCloth]);
|
|
4127
|
+
useEffect5(() => () => geometry.dispose(), [geometry]);
|
|
4128
|
+
const basePositions = useMemo3(
|
|
4129
|
+
() => Float32Array.from(geometry.attributes.position.array),
|
|
4130
|
+
[geometry]
|
|
4131
|
+
);
|
|
4132
|
+
const sim = useMemo3(() => {
|
|
4133
|
+
if (!isCloth) return null;
|
|
4134
|
+
const cloth = configRef.current.physics;
|
|
4135
|
+
const cols = geometry.parameters.widthSegments + 1;
|
|
4136
|
+
const rows = geometry.parameters.heightSegments + 1;
|
|
4125
4137
|
return new ClothSim(cols, rows, config.sheet.width, config.sheet.height, cloth.pins, {
|
|
4126
4138
|
stiffness: cloth.stiffness,
|
|
4127
4139
|
gravity: cloth.gravity,
|
|
@@ -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
|