leafthrough 0.1.6 → 0.1.7
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 +16 -6
- package/dist/{chunk-QFOGCGJJ.js → chunk-KCIOFDSW.js} +189 -66
- package/dist/chunk-KCIOFDSW.js.map +1 -0
- package/dist/element.js +1 -1
- package/dist/engine.d.ts +3 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/paper/paperPattern.d.ts +6 -0
- package/dist/react.js +1 -1
- package/dist/shared.d.ts +19 -0
- package/dist/single/SingleEngine.d.ts +2 -0
- package/dist/single/singleSheet.d.ts +6 -0
- package/dist/spread/SpreadEngine.d.ts +1 -0
- package/dist/spread/spreadSheet.d.ts +6 -0
- package/dist/svelte.js +1 -1
- package/dist/textures.d.ts +5 -0
- package/dist/vue.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-QFOGCGJJ.js.map +0 -1
package/README.md
CHANGED
|
@@ -190,6 +190,10 @@ renderer.setAnimationLoop((now) => {
|
|
|
190
190
|
`receiveShadow` on the sheets, which the magazine sets itself.
|
|
191
191
|
- **Depth.** See [Depth](#depth): the sheets stand a little apart, which needs enough depth
|
|
192
192
|
precision from your camera.
|
|
193
|
+
- **Several magazines.** Magazines drawn by the same renderer share the built-in grain's
|
|
194
|
+
texture and the sheet geometry of each layout and page aspect: made for the first, freed
|
|
195
|
+
with the last. The grain itself is drawn once per page, and the wrinkles are sampled once
|
|
196
|
+
per aspect.
|
|
193
197
|
- **Rendering on demand.** If you only draw when something changes, listen for `change`:
|
|
194
198
|
it fires when input, loading or a method call needs a frame. Then keep drawing while
|
|
195
199
|
`tick()` returns true.
|
|
@@ -311,14 +315,14 @@ The magazine fills the element you give it and keeps its proportions, centred.
|
|
|
311
315
|
| `formatPageLabel` | `(detail) => string` | `"Pages 14–15 of 28"` | What screen readers hear when pages change. |
|
|
312
316
|
| `onPageChange` | `(detail) => void` | | Same as the `pagechange` event. |
|
|
313
317
|
| `onReady` | `() => void` | | The first pages are loaded and on screen. |
|
|
314
|
-
| `onPageLoad` | `(page) => void` | | A page's image loaded. Same as the `pageload` event.
|
|
318
|
+
| `onPageLoad` | `(page) => void` | | A page's image is loaded and on the GPU. Same as the `pageload` event. |
|
|
315
319
|
| `onError` | `(error, page?) => void` | | A page failed to load. It stays blank paper until `retry()`. |
|
|
316
320
|
|
|
317
321
|
**`paper`**
|
|
318
322
|
|
|
319
323
|
| | Default | |
|
|
320
324
|
| ------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
321
|
-
| `grain` | `true` | `false` turns it off. You can also pass a tiling greyscale image or its URL; only its brightest specks show. |
|
|
325
|
+
| `grain` | `true` | `false` turns it off. You can also pass a tiling greyscale image or its URL; only its brightest specks show. The built-in one is drawn once per page, however many magazines there are. |
|
|
322
326
|
| `grainScale` | `1.46` | Tiles across a page. |
|
|
323
327
|
| `grainStrength` | `0.17` | How much the grain darkens. |
|
|
324
328
|
| `showThrough` | spread `0.04`, single `0.3` | How much of the other side shows through the paper. |
|
|
@@ -411,11 +415,17 @@ pages: [
|
|
|
411
415
|
front page's outline (draw the back page as its mirror image).
|
|
412
416
|
- **Cross-origin URLs.** URLs on another origin need CORS headers, because the images are
|
|
413
417
|
uploaded to WebGL.
|
|
414
|
-
- **Textures.** A three.js `Texture` you pass in is never disposed,
|
|
415
|
-
`colorSpace`, `premultiplyAlpha` and `anisotropy
|
|
416
|
-
|
|
418
|
+
- **Textures.** A three.js `Texture` you pass in is taken as it is: never disposed, and its
|
|
419
|
+
`colorSpace`, `premultiplyAlpha` and `anisotropy` are left alone, so one that's on the
|
|
420
|
+
GPU already, or that your own material uses too, isn't uploaded again. Pages the magazine
|
|
421
|
+
loads itself are sRGB and premultiplied; for yours to look the same, set `colorSpace` to
|
|
422
|
+
`THREE.SRGBColorSpace`, and `premultiplyAlpha` to `true` if it has transparent parts (the
|
|
423
|
+
shader lays it over the paper as premultiplied colour).
|
|
417
424
|
- **Load order.** Pages load nearest the reader first, four at a time, and the order follows
|
|
418
|
-
the reader as they turn or jump. Pages that haven't arrived show blank paper.
|
|
425
|
+
the reader as they turn or jump. Pages that haven't arrived show blank paper. Once decoded,
|
|
426
|
+
the pages in view and next to them go to the GPU at once; the rest go one per frame, or
|
|
427
|
+
more in idle time, so a dozen arriving together don't drop a frame. `pageload` fires for
|
|
428
|
+
each as it's on the GPU.
|
|
419
429
|
- **Memory.** Pages within `preload` of the visible ones load; pages more than twice that
|
|
420
430
|
far away unload, freeing their GPU memory, and load again when the reader comes back.
|
|
421
431
|
Visible means anything on screen: pages still turning in a quick riffle, and in `single`
|
|
@@ -250,11 +250,11 @@ function displayImage(source, alt = "") {
|
|
|
250
250
|
return img;
|
|
251
251
|
}
|
|
252
252
|
if (typeof HTMLCanvasElement !== "undefined" && (drawable instanceof HTMLCanvasElement || drawable instanceof ImageBitmap)) {
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return describe(
|
|
253
|
+
const canvas2 = document.createElement("canvas");
|
|
254
|
+
canvas2.width = drawable.width;
|
|
255
|
+
canvas2.height = drawable.height;
|
|
256
|
+
canvas2.getContext("2d")?.drawImage(drawable, 0, 0);
|
|
257
|
+
return describe(canvas2, alt, style);
|
|
258
258
|
}
|
|
259
259
|
return describe(document.createElement("div"), alt, style);
|
|
260
260
|
}
|
|
@@ -426,6 +426,10 @@ import * as THREE7 from "three";
|
|
|
426
426
|
// src/textures.ts
|
|
427
427
|
import * as THREE4 from "three";
|
|
428
428
|
var CONCURRENCY = 4;
|
|
429
|
+
var NEAR = 1;
|
|
430
|
+
var UPLOADS_PER_FRAME = 1;
|
|
431
|
+
var IDLE_SPARE = 8;
|
|
432
|
+
var UPLOAD_TIMEOUT = 100;
|
|
429
433
|
var PageLoader = class {
|
|
430
434
|
constructor(sources, renderer, radius, events) {
|
|
431
435
|
this.sources = sources;
|
|
@@ -443,6 +447,9 @@ var PageLoader = class {
|
|
|
443
447
|
owned = /* @__PURE__ */ new Set();
|
|
444
448
|
inFlight = /* @__PURE__ */ new Map();
|
|
445
449
|
failed = /* @__PURE__ */ new Set();
|
|
450
|
+
// decoded pages waiting for their turn on the GPU
|
|
451
|
+
pending = /* @__PURE__ */ new Map();
|
|
452
|
+
cancelUploads = null;
|
|
446
453
|
// `first` pages load before the rest; the rest go nearest first
|
|
447
454
|
first = [];
|
|
448
455
|
// the pages in view, and the one being read, which loads first among them
|
|
@@ -491,6 +498,15 @@ var PageLoader = class {
|
|
|
491
498
|
this.owned.delete(texture);
|
|
492
499
|
texture.dispose();
|
|
493
500
|
});
|
|
501
|
+
for (const [i, texture] of this.pending) {
|
|
502
|
+
if (this.distance(i) > keep) {
|
|
503
|
+
this.pending.delete(i);
|
|
504
|
+
if (this.owned.delete(texture)) texture.dispose();
|
|
505
|
+
} else if (this.distance(i) <= NEAR) {
|
|
506
|
+
this.pending.delete(i);
|
|
507
|
+
this.show(i, texture);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
494
510
|
this.pump();
|
|
495
511
|
}
|
|
496
512
|
setWrap(wrap) {
|
|
@@ -509,6 +525,9 @@ var PageLoader = class {
|
|
|
509
525
|
}
|
|
510
526
|
dispose() {
|
|
511
527
|
this.disposed = true;
|
|
528
|
+
this.cancelUploads?.();
|
|
529
|
+
this.cancelUploads = null;
|
|
530
|
+
this.pending.clear();
|
|
512
531
|
for (const request of this.inFlight.values()) request.abort();
|
|
513
532
|
for (const texture of this.owned) texture.dispose();
|
|
514
533
|
this.owned.clear();
|
|
@@ -523,7 +542,7 @@ var PageLoader = class {
|
|
|
523
542
|
// starts the most wanted pages while there's room
|
|
524
543
|
pump() {
|
|
525
544
|
if (this.disposed || this.inFlight.size >= CONCURRENCY) return;
|
|
526
|
-
const wanted = (i) => !this.textures[i] && !this.inFlight.has(i) && !this.failed.has(i) && this.distance(i) <= this.radius;
|
|
545
|
+
const wanted = (i) => !this.textures[i] && !this.inFlight.has(i) && !this.pending.has(i) && !this.failed.has(i) && this.distance(i) <= this.radius;
|
|
527
546
|
const queue = [
|
|
528
547
|
...this.first.filter((i) => this.waiting.has(i)),
|
|
529
548
|
...this.sources.map((_, i) => i).sort((a, b) => this.distance(a) - this.distance(b) || Math.abs(a - this.around) - Math.abs(b - this.around))
|
|
@@ -541,13 +560,17 @@ var PageLoader = class {
|
|
|
541
560
|
if (this.owned.delete(texture)) texture.dispose();
|
|
542
561
|
return;
|
|
543
562
|
}
|
|
544
|
-
texture
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
this.
|
|
563
|
+
if (this.owned.has(texture)) {
|
|
564
|
+
texture.colorSpace = THREE4.SRGBColorSpace;
|
|
565
|
+
texture.premultiplyAlpha = true;
|
|
566
|
+
texture.anisotropy = this.renderer.capabilities.getMaxAnisotropy();
|
|
567
|
+
texture.needsUpdate = true;
|
|
568
|
+
}
|
|
569
|
+
if (this.waiting.has(page) || this.distance(page) <= NEAR) this.show(page, texture);
|
|
570
|
+
else {
|
|
571
|
+
this.pending.set(page, texture);
|
|
572
|
+
this.scheduleUploads();
|
|
573
|
+
}
|
|
551
574
|
}).catch((error) => {
|
|
552
575
|
if (this.disposed || request.signal.aborted) return;
|
|
553
576
|
this.failed.add(page);
|
|
@@ -558,6 +581,41 @@ var PageLoader = class {
|
|
|
558
581
|
this.pump();
|
|
559
582
|
});
|
|
560
583
|
}
|
|
584
|
+
// Puts the page's texture on the GPU and hands it over. A texture passed in is taken as it
|
|
585
|
+
// is, so one that's on the GPU already, or shared with the caller's own material, isn't
|
|
586
|
+
// uploaded again: initTexture only uploads what the renderer hasn't got.
|
|
587
|
+
show(page, texture) {
|
|
588
|
+
this.renderer.initTexture(texture);
|
|
589
|
+
this.textures[page] = texture;
|
|
590
|
+
this.events.onLoad(page, texture);
|
|
591
|
+
}
|
|
592
|
+
// the next pending pages, in idle time where the browser offers it, else before the next frame
|
|
593
|
+
scheduleUploads() {
|
|
594
|
+
if (this.cancelUploads || !this.pending.size) return;
|
|
595
|
+
if (typeof requestIdleCallback === "function") {
|
|
596
|
+
const id = requestIdleCallback((deadline) => this.uploadSome(deadline), { timeout: UPLOAD_TIMEOUT });
|
|
597
|
+
this.cancelUploads = () => cancelIdleCallback(id);
|
|
598
|
+
} else {
|
|
599
|
+
const id = requestAnimationFrame(() => this.uploadSome());
|
|
600
|
+
this.cancelUploads = () => cancelAnimationFrame(id);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// nearest the reader first
|
|
604
|
+
uploadSome(deadline) {
|
|
605
|
+
this.cancelUploads = null;
|
|
606
|
+
const queue = [...this.pending.keys()].sort(
|
|
607
|
+
(a, b) => this.distance(a) - this.distance(b) || Math.abs(a - this.around) - Math.abs(b - this.around)
|
|
608
|
+
);
|
|
609
|
+
let count = 0;
|
|
610
|
+
for (const page of queue) {
|
|
611
|
+
if (count >= UPLOADS_PER_FRAME && !(deadline && deadline.timeRemaining() > IDLE_SPARE)) break;
|
|
612
|
+
const texture = this.pending.get(page);
|
|
613
|
+
this.pending.delete(page);
|
|
614
|
+
this.show(page, texture);
|
|
615
|
+
count++;
|
|
616
|
+
}
|
|
617
|
+
this.scheduleUploads();
|
|
618
|
+
}
|
|
561
619
|
async createTexture(source, signal) {
|
|
562
620
|
if (source instanceof THREE4.Texture) return source;
|
|
563
621
|
const own = (texture) => (this.owned.add(texture), texture);
|
|
@@ -567,11 +625,11 @@ var PageLoader = class {
|
|
|
567
625
|
if (!source.complete || !source.naturalWidth) await source.decode();
|
|
568
626
|
return own(new THREE4.Texture(source));
|
|
569
627
|
}
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return own(new THREE4.CanvasTexture(
|
|
628
|
+
const canvas2 = document.createElement("canvas");
|
|
629
|
+
canvas2.width = source.width;
|
|
630
|
+
canvas2.height = source.height;
|
|
631
|
+
canvas2.getContext("2d").drawImage(source, 0, 0);
|
|
632
|
+
return own(new THREE4.CanvasTexture(canvas2));
|
|
575
633
|
}
|
|
576
634
|
};
|
|
577
635
|
function loadImage(url, signal) {
|
|
@@ -815,6 +873,37 @@ function solidTexture(color) {
|
|
|
815
873
|
return texture;
|
|
816
874
|
}
|
|
817
875
|
|
|
876
|
+
// src/shared.ts
|
|
877
|
+
var SharedResources = class {
|
|
878
|
+
entries = /* @__PURE__ */ new Map();
|
|
879
|
+
/** The resource under `key`, made by `make` for the first user. Pair every call with `release`. */
|
|
880
|
+
acquire(key, make) {
|
|
881
|
+
let entry = this.entries.get(key);
|
|
882
|
+
if (!entry) {
|
|
883
|
+
entry = { value: make(), users: 0 };
|
|
884
|
+
this.entries.set(key, entry);
|
|
885
|
+
}
|
|
886
|
+
entry.users++;
|
|
887
|
+
return entry.value;
|
|
888
|
+
}
|
|
889
|
+
/** Lets go of `key`; the last user's release frees it. */
|
|
890
|
+
release(key) {
|
|
891
|
+
const entry = this.entries.get(key);
|
|
892
|
+
if (!entry || --entry.users > 0) return;
|
|
893
|
+
this.entries.delete(key);
|
|
894
|
+
entry.value.dispose();
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
var byRenderer = /* @__PURE__ */ new WeakMap();
|
|
898
|
+
function sharedResources(renderer) {
|
|
899
|
+
let shared = byRenderer.get(renderer);
|
|
900
|
+
if (!shared) {
|
|
901
|
+
shared = new SharedResources();
|
|
902
|
+
byRenderer.set(renderer, shared);
|
|
903
|
+
}
|
|
904
|
+
return shared;
|
|
905
|
+
}
|
|
906
|
+
|
|
818
907
|
// src/paper/paperPattern.ts
|
|
819
908
|
import * as THREE6 from "three";
|
|
820
909
|
var SIZE = 384;
|
|
@@ -865,7 +954,9 @@ function spread(values) {
|
|
|
865
954
|
for (const v of values) variance += (v - mean) ** 2;
|
|
866
955
|
return Math.sqrt(variance / values.length) || 1;
|
|
867
956
|
}
|
|
868
|
-
|
|
957
|
+
var canvas = null;
|
|
958
|
+
function paperPatternCanvas() {
|
|
959
|
+
if (canvas) return canvas;
|
|
869
960
|
let s = 7;
|
|
870
961
|
const random = () => (s = s * 1664525 + 1013904223 >>> 0) / 4294967296;
|
|
871
962
|
const noise = new Float32Array(SIZE * SIZE);
|
|
@@ -893,7 +984,7 @@ function createPaperPattern(renderer) {
|
|
|
893
984
|
const cloudScale = CLOUD * spread(grain) / spread(cloud);
|
|
894
985
|
for (let i = 0; i < grain.length; i++) grain[i] += cloud[i] * cloudScale;
|
|
895
986
|
const order = Array.from(grain.keys()).sort((a, b) => grain[a] - grain[b]);
|
|
896
|
-
|
|
987
|
+
canvas = document.createElement("canvas");
|
|
897
988
|
canvas.width = canvas.height = SIZE;
|
|
898
989
|
const ctx = canvas.getContext("2d");
|
|
899
990
|
const image = ctx.createImageData(SIZE, SIZE);
|
|
@@ -909,13 +1000,17 @@ function createPaperPattern(renderer) {
|
|
|
909
1000
|
image.data[o + 3] = 255;
|
|
910
1001
|
});
|
|
911
1002
|
ctx.putImageData(image, 0, 0);
|
|
912
|
-
|
|
1003
|
+
return canvas;
|
|
1004
|
+
}
|
|
1005
|
+
function createPaperPattern(renderer) {
|
|
1006
|
+
const texture = new THREE6.CanvasTexture(paperPatternCanvas());
|
|
913
1007
|
texture.wrapS = texture.wrapT = THREE6.RepeatWrapping;
|
|
914
1008
|
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
|
915
1009
|
return texture;
|
|
916
1010
|
}
|
|
917
1011
|
|
|
918
1012
|
// src/paper/paperTextures.ts
|
|
1013
|
+
var BUILTIN_PATTERN = "paper-pattern";
|
|
919
1014
|
var PaperTextures = class {
|
|
920
1015
|
constructor(backColor, events) {
|
|
921
1016
|
this.events = events;
|
|
@@ -940,30 +1035,34 @@ var PaperTextures = class {
|
|
|
940
1035
|
const grain = settings.paper.grain;
|
|
941
1036
|
const key = grain === true || grain === void 0 ? "builtin" : grain === false ? "none" : grain;
|
|
942
1037
|
if (this.pattern?.key === key) return this.pattern.texture;
|
|
943
|
-
this.pattern?.
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
texture
|
|
1038
|
+
this.pattern?.release();
|
|
1039
|
+
if (key === "builtin") {
|
|
1040
|
+
const shared = sharedResources(renderer);
|
|
1041
|
+
const texture = shared.acquire(BUILTIN_PATTERN, () => createPaperPattern(renderer));
|
|
1042
|
+
this.pattern = { texture, key, release: () => shared.release(BUILTIN_PATTERN) };
|
|
1043
|
+
return texture;
|
|
1044
|
+
}
|
|
1045
|
+
const pattern = { texture: solidTexture("#000000"), key, release: () => pattern.texture.dispose() };
|
|
1046
|
+
this.pattern = pattern;
|
|
1047
|
+
if (key !== "none") {
|
|
949
1048
|
loadGrain(key).then(
|
|
950
1049
|
(loaded) => {
|
|
951
|
-
if (this.pattern
|
|
952
|
-
|
|
953
|
-
|
|
1050
|
+
if (this.pattern !== pattern || this.disposed) return loaded.dispose();
|
|
1051
|
+
pattern.texture.dispose();
|
|
1052
|
+
pattern.texture = loaded;
|
|
954
1053
|
this.events.onGrain();
|
|
955
1054
|
},
|
|
956
1055
|
(error) => {
|
|
957
|
-
if (this.pattern
|
|
1056
|
+
if (this.pattern === pattern && !this.disposed) this.events.onError(error);
|
|
958
1057
|
}
|
|
959
1058
|
);
|
|
960
1059
|
}
|
|
961
|
-
|
|
962
|
-
return texture;
|
|
1060
|
+
return pattern.texture;
|
|
963
1061
|
}
|
|
964
1062
|
dispose() {
|
|
965
1063
|
this.disposed = true;
|
|
966
|
-
this.pattern?.
|
|
1064
|
+
this.pattern?.release();
|
|
1065
|
+
this.pattern = null;
|
|
967
1066
|
this.blank.dispose();
|
|
968
1067
|
this.backBlank.texture.dispose();
|
|
969
1068
|
}
|
|
@@ -1210,6 +1309,10 @@ var fragmentChunk = (
|
|
|
1210
1309
|
function createSingleGeometry(aspect) {
|
|
1211
1310
|
return new THREE9.PlaneGeometry(1, aspect, 64, Math.round(64 * aspect));
|
|
1212
1311
|
}
|
|
1312
|
+
function sharedSingleGeometry(shared, aspect) {
|
|
1313
|
+
const key = `single-geometry:${aspect}`;
|
|
1314
|
+
return { geometry: shared.acquire(key, () => createSingleGeometry(aspect)), release: () => shared.release(key) };
|
|
1315
|
+
}
|
|
1213
1316
|
function createSingleSheet(geometry, front, back, pattern, settings, shading) {
|
|
1214
1317
|
const uniforms = {
|
|
1215
1318
|
uPileLift: { value: new THREE9.Vector3(1, 0, 0) },
|
|
@@ -1310,6 +1413,7 @@ var SingleEngine = class {
|
|
|
1310
1413
|
metrics;
|
|
1311
1414
|
requestFrame;
|
|
1312
1415
|
geometry;
|
|
1416
|
+
releaseGeometry;
|
|
1313
1417
|
sheets;
|
|
1314
1418
|
count;
|
|
1315
1419
|
radiusSpacing;
|
|
@@ -1342,7 +1446,9 @@ var SingleEngine = class {
|
|
|
1342
1446
|
this.count = ctx.textures.length;
|
|
1343
1447
|
this.maxPosition = this.count;
|
|
1344
1448
|
this.current = clamp(Math.round(ctx.position), 0, Math.max(this.count - 1, 0));
|
|
1345
|
-
|
|
1449
|
+
const { geometry, release } = sharedSingleGeometry(ctx.shared, ctx.settings.pageAspect);
|
|
1450
|
+
this.geometry = geometry;
|
|
1451
|
+
this.releaseGeometry = release;
|
|
1346
1452
|
this.sheets = ctx.textures.map((front, index) => {
|
|
1347
1453
|
const { mesh, uniforms } = createSingleSheet(this.geometry, front, ctx.blank, ctx.pattern, ctx.settings, ctx.shading);
|
|
1348
1454
|
this.group.add(mesh);
|
|
@@ -1426,7 +1532,7 @@ var SingleEngine = class {
|
|
|
1426
1532
|
this.cancelHold();
|
|
1427
1533
|
this.abort.abort();
|
|
1428
1534
|
for (const sheet of this.sheets) disposeSheetMesh(sheet.mesh);
|
|
1429
|
-
this.
|
|
1535
|
+
this.releaseGeometry();
|
|
1430
1536
|
}
|
|
1431
1537
|
// --- helpers ----------------------------------------------------------------
|
|
1432
1538
|
randomWobble() {
|
|
@@ -1476,6 +1582,10 @@ var SingleEngine = class {
|
|
|
1476
1582
|
shutting(turned) {
|
|
1477
1583
|
return smooth((this.closing(turned) - 0.6) / 0.4);
|
|
1478
1584
|
}
|
|
1585
|
+
// how far a read page has settled flat under the pile: 0 still on the roll, 1 flat
|
|
1586
|
+
fallOf(sheet, paced, shut) {
|
|
1587
|
+
return Math.max(smooth((paced - (sheet.index + 1) - this.fallDelay) / this.fallDuration), shut);
|
|
1588
|
+
}
|
|
1479
1589
|
inFlight(sheet) {
|
|
1480
1590
|
return this.flights.some((f) => f.sheet === sheet);
|
|
1481
1591
|
}
|
|
@@ -1778,11 +1888,13 @@ var SingleEngine = class {
|
|
|
1778
1888
|
sheet.uniforms.uTrim.value.set(sheet === top || this.busy(sheet) ? 0 : 1, edge.bottom, edge.top);
|
|
1779
1889
|
}
|
|
1780
1890
|
const closing = this.closing(turned) > 0;
|
|
1891
|
+
const paced = this.paced(turned);
|
|
1892
|
+
const shut = this.shutting(turned);
|
|
1781
1893
|
const pileFrom = Math.max(this.current, top?.index ?? this.current);
|
|
1782
1894
|
for (const sheet of this.sheets) {
|
|
1783
1895
|
const i = sheet.index;
|
|
1784
1896
|
sheet.mesh.visible = closing || i <= pileFrom + PILE.renderDepth || this.busy(sheet);
|
|
1785
|
-
const depth = i
|
|
1897
|
+
const depth = i >= this.current ? i - this.current : this.fallOf(sheet, paced, shut) >= 1 ? this.count - this.current + i : this.current - 1 - i;
|
|
1786
1898
|
layerSheet(sheet.mesh, depth, this.count, this.busy(sheet), this.settings.paper.depthBias);
|
|
1787
1899
|
}
|
|
1788
1900
|
const read = smooth(this.count > 1 ? this.paced(turned) / (this.count - 1) : 0) * (1 - this.shutting(turned));
|
|
@@ -1815,10 +1927,7 @@ var SingleEngine = class {
|
|
|
1815
1927
|
const spineOpen = (ROLL.spineOpenStartDeg + (ROLL.spineOpenEndDeg - ROLL.spineOpenStartDeg) * Math.pow(openFraction, ROLL.spineOpenPower)) * Math.PI / 180;
|
|
1816
1928
|
const shut = this.shutting(turned);
|
|
1817
1929
|
const closed = this.closing(turned) >= 1 ? 1 : 0;
|
|
1818
|
-
const fall =
|
|
1819
|
-
smooth((paced - (sheet.index + 1) - this.fallDelay) / this.fallDuration),
|
|
1820
|
-
shut
|
|
1821
|
-
);
|
|
1930
|
+
const fall = this.fallOf(sheet, paced, shut);
|
|
1822
1931
|
const spacing = this.radiusSpacing[sheet.index] ?? 0;
|
|
1823
1932
|
const topR = ROLL.coverTopRadius + (ROLL.backTopRadius - ROLL.coverTopRadius) * spacing;
|
|
1824
1933
|
const bottomR = ROLL.coverBottomRadius + (ROLL.backBottomRadius - ROLL.coverBottomRadius) * spacing;
|
|
@@ -1861,7 +1970,7 @@ var SingleEngine = class {
|
|
|
1861
1970
|
const under = P - 1 - sheet.index;
|
|
1862
1971
|
const before = Math.max(0, turned - 1 - sheet.index);
|
|
1863
1972
|
const around = smooth(T);
|
|
1864
|
-
const offset = fallen ?
|
|
1973
|
+
const offset = fallen ? -(sheet.index + 1) : under + (before - under) * around;
|
|
1865
1974
|
u.uStackGap.value = this.settings.paper.sheetGap * (offset + (under - offset) * shut);
|
|
1866
1975
|
}
|
|
1867
1976
|
// a turning page: swing round the spine up to the open angle, then roll round the cone
|
|
@@ -2003,20 +2112,29 @@ function wrinkleNoise(u, v, aspect) {
|
|
|
2003
2112
|
}
|
|
2004
2113
|
return value;
|
|
2005
2114
|
}
|
|
2115
|
+
var noiseByAspect = /* @__PURE__ */ new Map();
|
|
2006
2116
|
function createSpreadGeometry(aspect) {
|
|
2007
2117
|
const geometry = new THREE11.PlaneGeometry(1, aspect, 64, Math.round(64 * aspect));
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2118
|
+
let noise = noiseByAspect.get(aspect);
|
|
2119
|
+
if (!noise) {
|
|
2120
|
+
const uv = geometry.attributes.uv;
|
|
2121
|
+
noise = new Float32Array(uv.count * 3);
|
|
2122
|
+
for (let i = 0; i < uv.count; i++) {
|
|
2123
|
+
const u = uv.getX(i);
|
|
2124
|
+
const v = uv.getY(i);
|
|
2125
|
+
noise[i * 3] = wrinkleNoise(u, v, aspect);
|
|
2126
|
+
noise[i * 3 + 1] = wrinkleNoise(u + NORMAL_EPSILON2, v, aspect);
|
|
2127
|
+
noise[i * 3 + 2] = wrinkleNoise(u, v + NORMAL_EPSILON2 / aspect, aspect);
|
|
2128
|
+
}
|
|
2129
|
+
noiseByAspect.set(aspect, noise);
|
|
2016
2130
|
}
|
|
2017
2131
|
geometry.setAttribute("aNoise", new THREE11.BufferAttribute(noise, 3));
|
|
2018
2132
|
return geometry;
|
|
2019
2133
|
}
|
|
2134
|
+
function sharedSpreadGeometry(shared, aspect) {
|
|
2135
|
+
const key = `spread-geometry:${aspect}`;
|
|
2136
|
+
return { geometry: shared.acquire(key, () => createSpreadGeometry(aspect)), release: () => shared.release(key) };
|
|
2137
|
+
}
|
|
2020
2138
|
function createSpreadSheet(geometry, front, back, pattern, settings, shading) {
|
|
2021
2139
|
const uniforms = {
|
|
2022
2140
|
uFlipProgress: { value: 0 },
|
|
@@ -2087,6 +2205,7 @@ var SpreadEngine = class {
|
|
|
2087
2205
|
metrics;
|
|
2088
2206
|
requestFrame;
|
|
2089
2207
|
geometry;
|
|
2208
|
+
releaseGeometry;
|
|
2090
2209
|
sheets;
|
|
2091
2210
|
abort = new AbortController();
|
|
2092
2211
|
shading;
|
|
@@ -2114,7 +2233,9 @@ var SpreadEngine = class {
|
|
|
2114
2233
|
const count = Math.ceil(ctx.textures.length / 2);
|
|
2115
2234
|
this.maxPosition = count;
|
|
2116
2235
|
this.current = clamp2(Math.round(ctx.position), 0, count);
|
|
2117
|
-
|
|
2236
|
+
const { geometry, release } = sharedSpreadGeometry(ctx.shared, ctx.settings.pageAspect);
|
|
2237
|
+
this.geometry = geometry;
|
|
2238
|
+
this.releaseGeometry = release;
|
|
2118
2239
|
this.sheets = [];
|
|
2119
2240
|
for (let i = 0; i < count; i++) {
|
|
2120
2241
|
const front = ctx.textures[2 * i];
|
|
@@ -2183,7 +2304,7 @@ var SpreadEngine = class {
|
|
|
2183
2304
|
this.cancelHold();
|
|
2184
2305
|
this.abort.abort();
|
|
2185
2306
|
for (const sheet of this.sheets) disposeSheetMesh(sheet.mesh);
|
|
2186
|
-
this.
|
|
2307
|
+
this.releaseGeometry();
|
|
2187
2308
|
}
|
|
2188
2309
|
// --- helpers ----------------------------------------------------------------
|
|
2189
2310
|
randomWobble() {
|
|
@@ -2876,6 +2997,7 @@ var LeafThrough = class extends EventTarget {
|
|
|
2876
2997
|
textures,
|
|
2877
2998
|
blank: layout === "single" ? this.paper.back(settings.paper.backColor) : blank,
|
|
2878
2999
|
pattern: this.paper.grain(settings, renderer),
|
|
3000
|
+
shared: sharedResources(renderer),
|
|
2879
3001
|
position: this.pageToPosition(page, layout),
|
|
2880
3002
|
metrics: this.metrics,
|
|
2881
3003
|
shading,
|
|
@@ -2928,18 +3050,18 @@ var LeafThrough = class extends EventTarget {
|
|
|
2928
3050
|
const right = Math.min(placed.view.x + placed.view.width, (documentWidth - pageLeft) / scaleX);
|
|
2929
3051
|
const top = Math.max(placed.view.y, -pageTop / scaleY);
|
|
2930
3052
|
const bottom = placed.view.y + placed.view.height;
|
|
2931
|
-
const
|
|
3053
|
+
const canvas2 = { x: left, y: top, width: Math.max(1, right - left), height: Math.max(1, bottom - top) };
|
|
2932
3054
|
const px = (n) => `${Math.round(n * 100) / 100}px`;
|
|
2933
3055
|
Object.assign(this.canvas.style, {
|
|
2934
|
-
left: px(
|
|
2935
|
-
top: px(
|
|
2936
|
-
width: px(
|
|
2937
|
-
height: px(
|
|
3056
|
+
left: px(canvas2.x),
|
|
3057
|
+
top: px(canvas2.y),
|
|
3058
|
+
width: px(canvas2.width),
|
|
3059
|
+
height: px(canvas2.height)
|
|
2938
3060
|
});
|
|
2939
3061
|
const maxRatio = Math.min(devicePixelRatio, settings.maxPixelRatio);
|
|
2940
|
-
const budget = Math.sqrt(15e6 / Math.max(1,
|
|
3062
|
+
const budget = Math.sqrt(15e6 / Math.max(1, canvas2.width * canvas2.height));
|
|
2941
3063
|
this.renderer.setPixelRatio(Math.min(maxRatio, budget));
|
|
2942
|
-
this.renderer.setSize(Math.max(1, Math.round(
|
|
3064
|
+
this.renderer.setSize(Math.max(1, Math.round(canvas2.width)), Math.max(1, Math.round(canvas2.height)), false);
|
|
2943
3065
|
const { eye, view, distance } = framing;
|
|
2944
3066
|
const halfWidth = Math.max(eye.x - view.x, view.x + view.width - eye.x);
|
|
2945
3067
|
const halfHeight = Math.max(eye.y - view.y, view.y + view.height - eye.y);
|
|
@@ -2951,10 +3073,10 @@ var LeafThrough = class extends EventTarget {
|
|
|
2951
3073
|
camera.setViewOffset(
|
|
2952
3074
|
2 * halfWidth * scale,
|
|
2953
3075
|
2 * halfHeight * scale,
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
3076
|
+
canvas2.x - frustumLeft,
|
|
3077
|
+
canvas2.y - frustumTop,
|
|
3078
|
+
canvas2.width,
|
|
3079
|
+
canvas2.height
|
|
2958
3080
|
);
|
|
2959
3081
|
camera.updateProjectionMatrix();
|
|
2960
3082
|
}
|
|
@@ -3122,10 +3244,10 @@ var LeafThrough = class extends EventTarget {
|
|
|
3122
3244
|
this.syncContent(view);
|
|
3123
3245
|
}
|
|
3124
3246
|
};
|
|
3125
|
-
function createRenderer(
|
|
3247
|
+
function createRenderer(canvas2) {
|
|
3126
3248
|
let renderer;
|
|
3127
3249
|
try {
|
|
3128
|
-
renderer = new THREE13.WebGLRenderer({ canvas, antialias: true, alpha: true });
|
|
3250
|
+
renderer = new THREE13.WebGLRenderer({ canvas: canvas2, antialias: true, alpha: true });
|
|
3129
3251
|
} catch {
|
|
3130
3252
|
return null;
|
|
3131
3253
|
}
|
|
@@ -3158,9 +3280,10 @@ export {
|
|
|
3158
3280
|
Riffle,
|
|
3159
3281
|
bindKeyboard,
|
|
3160
3282
|
PageLoader,
|
|
3283
|
+
sharedResources,
|
|
3161
3284
|
PaperTextures,
|
|
3162
3285
|
SingleEngine,
|
|
3163
3286
|
SpreadEngine,
|
|
3164
3287
|
LeafThrough
|
|
3165
3288
|
};
|
|
3166
|
-
//# sourceMappingURL=chunk-
|
|
3289
|
+
//# sourceMappingURL=chunk-KCIOFDSW.js.map
|