leafthrough 0.1.5 → 0.1.6
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 +79 -4
- package/dist/LeafThroughObject.d.ts +10 -2
- package/dist/{chunk-GYSQO7QZ.js → chunk-QFOGCGJJ.js} +73 -24
- package/dist/chunk-QFOGCGJJ.js.map +1 -0
- package/dist/element.js +1 -1
- package/dist/engine.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +47 -7
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +32 -1
- package/dist/paper/paperMaterial.d.ts +7 -5
- package/dist/react.js +1 -1
- package/dist/single/SingleEngine.d.ts +2 -0
- package/dist/single/singleSheet.d.ts +2 -0
- package/dist/spread/SpreadEngine.d.ts +1 -0
- package/dist/spread/spreadSheet.d.ts +2 -0
- package/dist/svelte.js +1 -1
- package/dist/vue.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-GYSQO7QZ.js.map +0 -1
package/README.md
CHANGED
|
@@ -145,7 +145,9 @@ draws with your renderer and takes pointer input through your camera, so it turn
|
|
|
145
145
|
it lies, however it's moved, turned or scaled. Every option, method, property and event of
|
|
146
146
|
`LeafThrough` works the same, apart from those about its own canvas and DOM: the options
|
|
147
147
|
`bleed`, `maxPixelRatio`, `plain`, `label` and `formatPageLabel`, and the properties
|
|
148
|
-
`plain` and `element`.
|
|
148
|
+
`plain` and `element`. It has one option of its own, `deform`
|
|
149
|
+
([Reshaping the paper](#reshaping-the-paper)), and its sheets stand apart by default
|
|
150
|
+
([Depth](#depth)).
|
|
149
151
|
|
|
150
152
|
```ts
|
|
151
153
|
import { LeafThroughObject } from 'leafthrough'
|
|
@@ -183,7 +185,11 @@ renderer.setAnimationLoop((now) => {
|
|
|
183
185
|
sheets cast and receive shadows when your renderer draws them, and `sun.position`
|
|
184
186
|
should point towards your shadow-casting directional light, in the magazine's own
|
|
185
187
|
frame (only the direction counts). It places the shadow a turning sheet casts right by
|
|
186
|
-
the spine, where the shadow map can't.
|
|
188
|
+
the spine, where the shadow map can't. `update({ lighting: { shadows } })` switches the
|
|
189
|
+
sheets' shadows without a rebuild. Use it rather than setting `castShadow` or
|
|
190
|
+
`receiveShadow` on the sheets, which the magazine sets itself.
|
|
191
|
+
- **Depth.** See [Depth](#depth): the sheets stand a little apart, which needs enough depth
|
|
192
|
+
precision from your camera.
|
|
187
193
|
- **Rendering on demand.** If you only draw when something changes, listen for `change`:
|
|
188
194
|
it fires when input, loading or a method call needs a frame. Then keep drawing while
|
|
189
195
|
`tick()` returns true.
|
|
@@ -197,11 +203,72 @@ magazine.covers(e.clientX, e.clientY) // whether the magazine takes a press t
|
|
|
197
203
|
magazine.bounds // { pages, reach }, in the magazine's own units
|
|
198
204
|
magazine.camera = otherCamera // cast input from another camera
|
|
199
205
|
magazine.enabled = false // cancel active gestures and stop taking pointer/key input
|
|
206
|
+
magazine.update({ lighting: { shadows: false } }) // sheets stop casting and receiving, no rebuild
|
|
200
207
|
magazine.addEventListener('change', () => requestRender())
|
|
201
208
|
magazine.addEventListener('pagechange', (e) => e.detail) // also ready, pageload, error
|
|
202
209
|
magazine.dispose() // free everything and leave the scene
|
|
203
210
|
```
|
|
204
211
|
|
|
212
|
+
### Depth
|
|
213
|
+
|
|
214
|
+
In a `LeafThroughObject` the sheets stand apart, `paper.sheetGap` `0.0005` page widths
|
|
215
|
+
(0.1 mm paper on a 210 mm page), and sort by their real depth, so they stay in order from the front, from
|
|
216
|
+
behind and at any angle. Your camera has to resolve that gap. A 24-bit depth buffer tells
|
|
217
|
+
apart points roughly `distance² / (near × 16,777,216)` apart, in your scene's units, and that
|
|
218
|
+
must be well under the gap in the same units. Take a booklet 120 mm wide in a scene in
|
|
219
|
+
metres, seen from 4.4 m: its gap is 0.06 mm. With the camera's `near` at 0.1 the buffer
|
|
220
|
+
resolves about 0.012 mm, which is enough; at 0.01 it's about 0.12 mm, and hidden pages
|
|
221
|
+
flicker through. So keep `near` as large as your scene allows; `far` matters much less.
|
|
222
|
+
The magazine never changes your camera. With a coarse depth buffer you can widen the gap.
|
|
223
|
+
|
|
224
|
+
The standalone magazine instead draws sheets that touch at the spine and lets
|
|
225
|
+
`paper.depthBias` sort them: a polygon offset brings each sheet nearer the camera than
|
|
226
|
+
those under it. You can turn that on here too (`paper: { sheetGap: 0, depthBias: true }`),
|
|
227
|
+
but it has two limits in a scene of your own. It assumes the magazine is seen from the
|
|
228
|
+
front, so from behind it brings the wrong sheets forward. And it moves sheets by steps of
|
|
229
|
+
the depth buffer, so how far depends on `near`, `far` and distance: with a small `near`
|
|
230
|
+
it can pull pages through whatever lies just in front of the magazine.
|
|
231
|
+
|
|
232
|
+
### Reshaping the paper
|
|
233
|
+
|
|
234
|
+
Experimental: this may change in a patch release. `deform` reshapes the paper with your
|
|
235
|
+
own GLSL, after the magazine has shaped it: bend a booklet coming out of its case, squeeze
|
|
236
|
+
it, roll it. It applies wherever the paper is drawn, in colour and in the shadow passes,
|
|
237
|
+
and lighting follows, because normals come from the deformed shape.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const bend = { value: 0 }
|
|
241
|
+
const magazine = new LeafThroughObject({ renderer, camera }, {
|
|
242
|
+
pages,
|
|
243
|
+
deform: {
|
|
244
|
+
uniforms: { uBend: bend },
|
|
245
|
+
glsl: /* glsl */ `
|
|
246
|
+
uniform float uBend;
|
|
247
|
+
vec3 deformSheet(vec3 p, vec2 uv) {
|
|
248
|
+
// curl the pages up along x, around a cylinder of radius 1 / uBend
|
|
249
|
+
if (uBend < 1e-4) return p;
|
|
250
|
+
float a = p.x * uBend;
|
|
251
|
+
float r = 1.0 / uBend - p.z;
|
|
252
|
+
return vec3(r * sin(a), p.y, 1.0 / uBend - r * cos(a));
|
|
253
|
+
}`,
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
bend.value = 1.2 // read on the next frame: no rebuild
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
- `deformSheet` gets `p`, a point of a sheet in the magazine's own units (see Units
|
|
261
|
+
above), and `uv`, where it is on that sheet: x from the spine (0) to the outer edge (1),
|
|
262
|
+
y from bottom to top. It returns where to draw that point, in the same units.
|
|
263
|
+
- three.js's common GLSL (`PI`, `saturate` and so on) is available. Name your uniforms
|
|
264
|
+
with a prefix of your own, as the magazine's start with `u`.
|
|
265
|
+
- Uniforms are read every frame: set `value` on your own objects, or pass new objects
|
|
266
|
+
under the same names through `update()`. Changing the GLSL, or which uniforms there are,
|
|
267
|
+
rebuilds the pages.
|
|
268
|
+
- It changes only what's drawn. Pointer input, `covers()`, `bounds` and the shadow a
|
|
269
|
+
turning sheet casts by the spine still work from the undeformed pages, so turn input off
|
|
270
|
+
(`enabled = false`) while the paper is far from its own shape.
|
|
271
|
+
|
|
205
272
|
The [three.js demo](https://dmtrbrl.github.io/leafthrough/three.html) takes a magazine
|
|
206
273
|
out of a portrait paperboard mailer on a table; its source is
|
|
207
274
|
[`demo/main-three.ts`](https://github.com/dmtrbrl/leafthrough/blob/main/demo/main-three.ts),
|
|
@@ -261,6 +328,8 @@ The magazine fills the element you give it and keeps its proportions, centred.
|
|
|
261
328
|
| `wrinkles` | `1` | Waviness of resting sheets; `0` lays them flat. |
|
|
262
329
|
| `bulge` | `1` | How much pages rise off the spine. |
|
|
263
330
|
| `cutout` | `false` | Cuts each sheet to the shape of its front page: transparent areas are no paper at all, shadow included. |
|
|
331
|
+
| `sheetGap` | `0` (`0.0005` in a scene) | Space between stacked sheets, in page widths. 0.1 mm paper on a 120 mm page is `0.1 / 120`. |
|
|
332
|
+
| `depthBias` | `true` (`false` in a scene) | Draws each sheet nearer the camera than those under it, so sheets touching at the spine don't flicker. See [Depth](#depth). |
|
|
264
333
|
|
|
265
334
|
**`lighting`**: `ambient: { sky, ground, intensity }`, `sun: { color, intensity, position }`,
|
|
266
335
|
`shadows` (default `true`), `shadowMapSize` (default `2048`), and `floorShadow`. That last
|
|
@@ -311,7 +380,8 @@ magazine.addEventListener('error', (e) => e.detail) // { error, page }
|
|
|
311
380
|
|
|
312
381
|
Both compare options by value. Changing callbacks or the label is free. Changing `pages`,
|
|
313
382
|
`layout`, paper, lighting, motion or interaction rebuilds the scene (a few milliseconds)
|
|
314
|
-
and keeps the reader on the same page.
|
|
383
|
+
and keeps the reader on the same page. In a `LeafThroughObject`, `lighting.shadows` and
|
|
384
|
+
new values for `deform`'s uniforms apply without a rebuild. `startPage` only applies at creation; use `goTo()`
|
|
315
385
|
after that. The React, Vue and Svelte bindings and the element use `setOptions()`, so
|
|
316
386
|
removing a prop or attribute restores its default. Options are copied on every call, so an
|
|
317
387
|
object or array changed in place and passed again (as Vue's reactive props are) counts as
|
|
@@ -402,7 +472,10 @@ How it works:
|
|
|
402
472
|
- `content.ts` keeps the visible pages' content as DOM: hidden for screen readers in 3D,
|
|
403
473
|
the pages themselves in the plain view.
|
|
404
474
|
- `textures.ts` loads, prioritises and unloads page textures.
|
|
405
|
-
- `src/paper` holds the shared paper material and the procedural grain.
|
|
475
|
+
- `src/paper` holds the shared paper material and the procedural grain. Sheets keep their
|
|
476
|
+
order by real depth, each lifted by `paper.sheetGap` for the sheets under it, or by a
|
|
477
|
+
polygon offset (`layerSheet`) where they touch. `deform` wraps the layout's sheet shape
|
|
478
|
+
in every pass.
|
|
406
479
|
- `LeafThrough.ts` handles the DOM, sizing, loading, layout switching, keyboard and
|
|
407
480
|
events. `LeafThroughObject.ts` does the same inside someone else's scene, without the DOM.
|
|
408
481
|
- Engines take input through `EngineInput` (`engine.ts`): an element to listen on, a ray
|
|
@@ -410,6 +483,8 @@ How it works:
|
|
|
410
483
|
their own floor plane, so the same code serves the magazine's own camera and any scene's.
|
|
411
484
|
- `navigation.ts` maps pages to engine positions, riffles an animated `goTo()` and binds
|
|
412
485
|
the keyboard, for both.
|
|
486
|
+
- `test/depth.test.ts` prints the pages that should be hidden pure red and checks that
|
|
487
|
+
none shows, across layouts, views, paper shapes, turns and camera depth ranges.
|
|
413
488
|
|
|
414
489
|
## Credits
|
|
415
490
|
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { type Rect } from './framing.js';
|
|
3
3
|
import type { GoToOptions } from './LeafThrough.js';
|
|
4
|
-
import { type LeafThroughOptions, type Layout, type PageChangeDetail } from './options.js';
|
|
4
|
+
import { type LeafThroughOptions, type Layout, type PageChangeDetail, type PaperDeform } from './options.js';
|
|
5
5
|
/**
|
|
6
6
|
* The magazine's options, less those about its own canvas and DOM. Of `lighting`, only
|
|
7
7
|
* `shadows` (whether sheets cast and receive them) and `sun.position` apply: the lights are
|
|
8
8
|
* your scene's. Point `sun.position` towards your shadow-casting directional light, in the
|
|
9
9
|
* magazine's own frame; it places the shadow a turning sheet casts right by the spine.
|
|
10
|
+
* `lighting.shadows` changes without a rebuild.
|
|
11
|
+
*
|
|
12
|
+
* Its sheets stand apart by default (`paper.sheetGap` `0.0005`, about 0.1 mm paper on a
|
|
13
|
+
* 210 mm page) and `paper.depthBias` is off, so they sort by real depth from any side.
|
|
10
14
|
*/
|
|
11
|
-
export type LeafThroughObjectOptions = Omit<LeafThroughOptions, 'bleed' | 'maxPixelRatio' | 'plain' | 'label' | 'formatPageLabel'
|
|
15
|
+
export type LeafThroughObjectOptions = Omit<LeafThroughOptions, 'bleed' | 'maxPixelRatio' | 'plain' | 'label' | 'formatPageLabel'> & {
|
|
16
|
+
/** Reshapes the paper in your own GLSL. Experimental. */
|
|
17
|
+
deform?: PaperDeform;
|
|
18
|
+
};
|
|
12
19
|
/** What the magazine draws with, from your scene. */
|
|
13
20
|
export interface LeafThroughObjectContext {
|
|
14
21
|
/** The renderer drawing your scene. Pages load into it. */
|
|
@@ -138,6 +145,7 @@ export declare class LeafThroughObject extends THREE.Group<LeafThroughObjectEven
|
|
|
138
145
|
private pickLayout;
|
|
139
146
|
private watchSpreadQuery;
|
|
140
147
|
private remount;
|
|
148
|
+
private resolve;
|
|
141
149
|
private readonly axis;
|
|
142
150
|
private pageAxis;
|
|
143
151
|
private dragWidth;
|
|
@@ -33,8 +33,11 @@ function resolveSettings(options, layout, prefersReducedMotion) {
|
|
|
33
33
|
backColor: paper.backColor ?? "#fcfcf9",
|
|
34
34
|
wrinkles: paper.wrinkles ?? 1,
|
|
35
35
|
bulge: paper.bulge ?? 1,
|
|
36
|
-
cutout: paper.cutout ?? false
|
|
36
|
+
cutout: paper.cutout ?? false,
|
|
37
|
+
sheetGap: Math.max(0, paper.sheetGap ?? 0),
|
|
38
|
+
depthBias: paper.depthBias ?? true
|
|
37
39
|
},
|
|
40
|
+
deform: null,
|
|
38
41
|
lighting: {
|
|
39
42
|
ambient: {
|
|
40
43
|
sky: lighting.ambient?.sky ?? "#ffffff",
|
|
@@ -632,27 +635,37 @@ function createPaperMaterials(uniforms, shader, settings) {
|
|
|
632
635
|
`
|
|
633
636
|
);
|
|
634
637
|
const noiseAt = (component) => shader.noise ? `aNoise.${component}` : "0.0";
|
|
638
|
+
const deform = settings.deform;
|
|
639
|
+
const deformChunk = deform ? `#include <common>
|
|
640
|
+
uniform vec3 uSheetOrigin;
|
|
641
|
+
${deform.glsl}
|
|
642
|
+
vec3 sheetDeformed(vec3 p, vec2 uv) {
|
|
643
|
+
return deformSheet(p + uSheetOrigin, uv) - uSheetOrigin;
|
|
644
|
+
}` : "#include <common>";
|
|
645
|
+
const deformed = (p, uv) => deform ? `sheetDeformed(${p}, ${uv})` : p;
|
|
646
|
+
const deformUniforms = deform ? { ...deform.uniforms, uSheetOrigin: uniforms.uSheetOrigin } : {};
|
|
635
647
|
const material = new THREE5.MeshStandardMaterial({
|
|
636
648
|
side: THREE5.DoubleSide,
|
|
637
649
|
metalness: paper.metalness,
|
|
638
650
|
roughness: paper.roughness
|
|
639
651
|
});
|
|
640
652
|
material.onBeforeCompile = (program) => {
|
|
641
|
-
Object.assign(program.uniforms, uniforms);
|
|
653
|
+
Object.assign(program.uniforms, uniforms, deformUniforms);
|
|
642
654
|
program.vertexShader = constants + shader.vertexChunk + "varying vec2 vSheetUv;\nvarying vec3 vSheetPosition;\n" + program.vertexShader;
|
|
643
|
-
program.vertexShader = program.vertexShader.replace(
|
|
655
|
+
program.vertexShader = program.vertexShader.replace("#include <common>", deformChunk).replace(
|
|
644
656
|
"#include <beginnormal_vertex>",
|
|
645
657
|
`#include <beginnormal_vertex>
|
|
646
658
|
vec2 sheetStep = vec2(NORMAL_EPSILON, NORMAL_EPSILON / SHEET_ASPECT);
|
|
647
|
-
vec3
|
|
648
|
-
vec3
|
|
649
|
-
vec3
|
|
659
|
+
vec3 sheetShaped = sheetPosition(uv, ${noiseAt("x")});
|
|
660
|
+
vec3 sheetP = ${deformed("sheetShaped", "uv")};
|
|
661
|
+
vec3 sheetPx = ${deformed(`sheetPosition(uv + vec2(sheetStep.x, 0.0), ${noiseAt("y")})`, "uv + vec2(sheetStep.x, 0.0)")};
|
|
662
|
+
vec3 sheetPy = ${deformed(`sheetPosition(uv + vec2(0.0, sheetStep.y), ${noiseAt("z")})`, "uv + vec2(0.0, sheetStep.y)")};
|
|
650
663
|
objectNormal = normalize(cross(sheetPx - sheetP, sheetPy - sheetP));`
|
|
651
664
|
).replace(
|
|
652
665
|
"#include <begin_vertex>",
|
|
653
666
|
`vec3 transformed = sheetP;
|
|
654
667
|
vSheetUv = uv;
|
|
655
|
-
vSheetPosition =
|
|
668
|
+
vSheetPosition = sheetShaped;`
|
|
656
669
|
);
|
|
657
670
|
program.fragmentShader = constants + `uniform sampler2D uFront;
|
|
658
671
|
uniform sampler2D uBack;
|
|
@@ -732,13 +745,13 @@ function createPaperMaterials(uniforms, shader, settings) {
|
|
|
732
745
|
});
|
|
733
746
|
const distanceMaterial = new THREE5.MeshDistanceMaterial({ side: THREE5.DoubleSide });
|
|
734
747
|
depthMaterial.onBeforeCompile = distanceMaterial.onBeforeCompile = (program) => {
|
|
735
|
-
Object.assign(program.uniforms, uniforms);
|
|
748
|
+
Object.assign(program.uniforms, uniforms, deformUniforms);
|
|
736
749
|
program.vertexShader = constants + shader.vertexChunk + "varying vec2 vSheetUv;\nvarying vec3 vSheetPosition;\n" + program.vertexShader;
|
|
737
|
-
program.vertexShader = program.vertexShader.replace(
|
|
750
|
+
program.vertexShader = program.vertexShader.replace("#include <common>", deformChunk).replace(
|
|
738
751
|
"#include <begin_vertex>",
|
|
739
752
|
`vSheetUv = uv;
|
|
740
753
|
vSheetPosition = sheetPosition(uv, ${noiseAt("x")});
|
|
741
|
-
vec3 transformed = vSheetPosition - vec3(SHADOW_OFFSET_X, 0.0, 0.0);`
|
|
754
|
+
vec3 transformed = ${deformed("vSheetPosition - vec3(SHADOW_OFFSET_X, 0.0, 0.0)", "uv")};`
|
|
742
755
|
);
|
|
743
756
|
program.fragmentShader = constants + `
|
|
744
757
|
uniform sampler2D uFront;
|
|
@@ -753,7 +766,7 @@ function createPaperMaterials(uniforms, shader, settings) {
|
|
|
753
766
|
${shader.fragmentChunk ? "if (sheetHidden(vSheetPosition)) discard;" : ""}`
|
|
754
767
|
);
|
|
755
768
|
};
|
|
756
|
-
const cacheKey = constants + shader.vertexChunk + (shader.fragmentChunk ?? "") + (paper.cutout ? "cutout" : "");
|
|
769
|
+
const cacheKey = constants + shader.vertexChunk + (shader.fragmentChunk ?? "") + (paper.cutout ? "cutout" : "") + (deform?.glsl ?? "");
|
|
757
770
|
material.customProgramCacheKey = () => cacheKey;
|
|
758
771
|
depthMaterial.customProgramCacheKey = () => cacheKey;
|
|
759
772
|
distanceMaterial.customProgramCacheKey = () => cacheKey;
|
|
@@ -777,11 +790,11 @@ function createSheetMesh(geometry, uniforms, shader, settings) {
|
|
|
777
790
|
mesh.frustumCulled = false;
|
|
778
791
|
return mesh;
|
|
779
792
|
}
|
|
780
|
-
function layerSheet(mesh, depth, count, moving) {
|
|
793
|
+
function layerSheet(mesh, depth, count, moving, bias) {
|
|
781
794
|
const material = mesh.material;
|
|
782
|
-
material.polygonOffset =
|
|
795
|
+
material.polygonOffset = bias;
|
|
783
796
|
material.polygonOffsetFactor = 0;
|
|
784
|
-
material.polygonOffsetUnits = moving ? -(count + 4) : -(count - Math.min(depth, count));
|
|
797
|
+
material.polygonOffsetUnits = bias ? moving ? -(count + 4) : -(count - Math.min(depth, count)) : 0;
|
|
785
798
|
mesh.renderOrder = depth;
|
|
786
799
|
}
|
|
787
800
|
function disposeSheetMesh(mesh) {
|
|
@@ -1085,6 +1098,7 @@ var vertexChunk = (
|
|
|
1085
1098
|
uniform float uTailBend; // how far the free end bows back
|
|
1086
1099
|
uniform vec3 uCone; // radius change per unit of y, radius at the top, how far to wrap
|
|
1087
1100
|
uniform vec2 uFlipRotation;
|
|
1101
|
+
uniform float uStackGap; // how far the sheet stands off the spine, along its own normal
|
|
1088
1102
|
|
|
1089
1103
|
float sheetPileLift(float x) {
|
|
1090
1104
|
float t = 1.0 - min(x / uPileLift.x, 1.0);
|
|
@@ -1170,7 +1184,7 @@ var vertexChunk = (
|
|
|
1170
1184
|
}
|
|
1171
1185
|
|
|
1172
1186
|
// spin about the spine
|
|
1173
|
-
float z = shape.z + pile.z;
|
|
1187
|
+
float z = shape.z + pile.z + uStackGap;
|
|
1174
1188
|
return vec3(
|
|
1175
1189
|
shape.x * uFlipRotation.x - z * uFlipRotation.y,
|
|
1176
1190
|
shape.y,
|
|
@@ -1206,11 +1220,13 @@ function createSingleSheet(geometry, front, back, pattern, settings, shading) {
|
|
|
1206
1220
|
uCone: { value: new THREE9.Vector3(0, 1, 0) },
|
|
1207
1221
|
uFlipRotation: { value: new THREE9.Vector2(1, 0) },
|
|
1208
1222
|
uTrim: { value: new THREE9.Vector3(0, 1, 1) },
|
|
1223
|
+
uStackGap: { value: 0 },
|
|
1209
1224
|
uFront: { value: front },
|
|
1210
1225
|
uBack: { value: back },
|
|
1211
1226
|
uPattern: { value: pattern },
|
|
1212
1227
|
uSpineContact: shading.spineContact,
|
|
1213
|
-
uLightDirection: shading.lightDirection
|
|
1228
|
+
uLightDirection: shading.lightDirection,
|
|
1229
|
+
uSheetOrigin: shading.origin
|
|
1214
1230
|
};
|
|
1215
1231
|
const mesh = createSheetMesh(
|
|
1216
1232
|
geometry,
|
|
@@ -1301,6 +1317,7 @@ var SingleEngine = class {
|
|
|
1301
1317
|
fallDelay;
|
|
1302
1318
|
fallDuration;
|
|
1303
1319
|
abort = new AbortController();
|
|
1320
|
+
shading;
|
|
1304
1321
|
current;
|
|
1305
1322
|
turned = 0;
|
|
1306
1323
|
// sum of every sheet's progress, as of the last update
|
|
@@ -1321,6 +1338,7 @@ var SingleEngine = class {
|
|
|
1321
1338
|
this.input = ctx.input;
|
|
1322
1339
|
this.metrics = ctx.metrics;
|
|
1323
1340
|
this.requestFrame = ctx.requestFrame;
|
|
1341
|
+
this.shading = ctx.shading;
|
|
1324
1342
|
this.count = ctx.textures.length;
|
|
1325
1343
|
this.maxPosition = this.count;
|
|
1326
1344
|
this.current = clamp(Math.round(ctx.position), 0, Math.max(this.count - 1, 0));
|
|
@@ -1399,6 +1417,11 @@ var SingleEngine = class {
|
|
|
1399
1417
|
if (this.press) this.release(new PointerEvent("pointercancel", { pointerId: this.press.id }));
|
|
1400
1418
|
this.wake();
|
|
1401
1419
|
}
|
|
1420
|
+
setShadows(on) {
|
|
1421
|
+
this.settings.lighting.shadows = on;
|
|
1422
|
+
for (const sheet of this.sheets) sheet.mesh.castShadow = sheet.mesh.receiveShadow = on;
|
|
1423
|
+
this.wake();
|
|
1424
|
+
}
|
|
1402
1425
|
dispose() {
|
|
1403
1426
|
this.cancelHold();
|
|
1404
1427
|
this.abort.abort();
|
|
@@ -1759,11 +1782,13 @@ var SingleEngine = class {
|
|
|
1759
1782
|
for (const sheet of this.sheets) {
|
|
1760
1783
|
const i = sheet.index;
|
|
1761
1784
|
sheet.mesh.visible = closing || i <= pileFrom + PILE.renderDepth || this.busy(sheet);
|
|
1762
|
-
|
|
1785
|
+
const depth = i < this.current ? this.current - 1 - i : i - this.current;
|
|
1786
|
+
layerSheet(sheet.mesh, depth, this.count, this.busy(sheet), this.settings.paper.depthBias);
|
|
1763
1787
|
}
|
|
1764
1788
|
const read = smooth(this.count > 1 ? this.paced(turned) / (this.count - 1) : 0) * (1 - this.shutting(turned));
|
|
1765
1789
|
this.group.position.x = BOOK.pushRight * read;
|
|
1766
1790
|
this.group.position.z = -BOOK.pushBack * read;
|
|
1791
|
+
this.shading.origin.value.copy(this.group.position);
|
|
1767
1792
|
this.dirty = false;
|
|
1768
1793
|
return true;
|
|
1769
1794
|
}
|
|
@@ -1833,6 +1858,11 @@ var SingleEngine = class {
|
|
|
1833
1858
|
topReach = Math.min(topReach, edge.top);
|
|
1834
1859
|
}
|
|
1835
1860
|
u.uPileReach.value.set(bottomReach, topReach);
|
|
1861
|
+
const under = P - 1 - sheet.index;
|
|
1862
|
+
const before = Math.max(0, turned - 1 - sheet.index);
|
|
1863
|
+
const around = smooth(T);
|
|
1864
|
+
const offset = fallen ? before * Math.cos(spineOpen) : under + (before - under) * around;
|
|
1865
|
+
u.uStackGap.value = this.settings.paper.sheetGap * (offset + (under - offset) * shut);
|
|
1836
1866
|
}
|
|
1837
1867
|
// a turning page: swing round the spine up to the open angle, then roll round the cone
|
|
1838
1868
|
shapeTurn(sheet, T, spineOpen, radii, tilt) {
|
|
@@ -1891,6 +1921,7 @@ var vertexChunk2 = (settings) => (
|
|
|
1891
1921
|
|
|
1892
1922
|
const float BULGE = ${settings.paper.bulge.toFixed(4)};
|
|
1893
1923
|
const float WRINKLE = ${(0.13 * settings.paper.wrinkles).toFixed(4)};
|
|
1924
|
+
const bool NESTED = ${settings.paper.depthBias ? "false" : "true"};
|
|
1894
1925
|
|
|
1895
1926
|
uniform float uFlipProgress;
|
|
1896
1927
|
uniform float uWrinkleSide;
|
|
@@ -1900,6 +1931,7 @@ var vertexChunk2 = (settings) => (
|
|
|
1900
1931
|
uniform vec2 uFlipRotation;
|
|
1901
1932
|
uniform float uDirection;
|
|
1902
1933
|
uniform float uStackLift;
|
|
1934
|
+
uniform float uStackGap;
|
|
1903
1935
|
|
|
1904
1936
|
attribute vec3 aNoise;
|
|
1905
1937
|
|
|
@@ -1916,8 +1948,11 @@ var vertexChunk2 = (settings) => (
|
|
|
1916
1948
|
float y = (uv.y - 0.5) * SHEET_ASPECT;
|
|
1917
1949
|
|
|
1918
1950
|
// Every sheet shares the same wrinkles and bulge, scaled by its place in the stack,
|
|
1919
|
-
// so stacked sheets nest inside each other instead of crossing.
|
|
1920
|
-
|
|
1951
|
+
// so stacked sheets nest inside each other instead of crossing. Where a wrinkle dips, a
|
|
1952
|
+
// bigger one dips below the sheets under it, which only the depth bias hides; without it
|
|
1953
|
+
// the dips are the same on every sheet.
|
|
1954
|
+
float wrinkle = uWrinkleSide * (mix(noise, 1.0 - noise, uFlipProgress) - 0.5) * smoothstep(0.0, 0.35, x) * WRINKLE;
|
|
1955
|
+
wrinkle *= NESTED && wrinkle < 0.0 ? 1.0 : uStackLift;
|
|
1921
1956
|
float lift = sheetRestingLift(x) * BULGE * uStackLift;
|
|
1922
1957
|
|
|
1923
1958
|
// bend with constant curvature past the fold line, trailing against the turn
|
|
@@ -1935,7 +1970,7 @@ var vertexChunk2 = (settings) => (
|
|
|
1935
1970
|
// turn about the spine
|
|
1936
1971
|
p = vec3(p.x * uFlipRotation.x - p.z * uFlipRotation.y, p.y, p.x * uFlipRotation.y + p.z * uFlipRotation.x);
|
|
1937
1972
|
|
|
1938
|
-
p.z += lift +
|
|
1973
|
+
p.z += lift + wrinkle + uStackGap;
|
|
1939
1974
|
return p;
|
|
1940
1975
|
}
|
|
1941
1976
|
`
|
|
@@ -1992,11 +2027,13 @@ function createSpreadSheet(geometry, front, back, pattern, settings, shading) {
|
|
|
1992
2027
|
uFlipRotation: { value: new THREE11.Vector2(1, 0) },
|
|
1993
2028
|
uDirection: { value: 1 },
|
|
1994
2029
|
uStackLift: { value: 1 },
|
|
2030
|
+
uStackGap: { value: 0 },
|
|
1995
2031
|
uFront: { value: front },
|
|
1996
2032
|
uBack: { value: back },
|
|
1997
2033
|
uPattern: { value: pattern },
|
|
1998
2034
|
uSpineContact: shading.spineContact,
|
|
1999
|
-
uLightDirection: shading.lightDirection
|
|
2035
|
+
uLightDirection: shading.lightDirection,
|
|
2036
|
+
uSheetOrigin: shading.origin
|
|
2000
2037
|
};
|
|
2001
2038
|
const mesh = createSheetMesh(
|
|
2002
2039
|
geometry,
|
|
@@ -2037,6 +2074,10 @@ function stackLift(index, count, progress) {
|
|
|
2037
2074
|
if (count <= 1) return 1;
|
|
2038
2075
|
return 0.31 + 0.69 * (index + (count - 1 - 2 * index) * (1 - progress)) / (count - 1);
|
|
2039
2076
|
}
|
|
2077
|
+
function sheetsUnder(index, count, progress) {
|
|
2078
|
+
const t = clamp2((progress - 0.25) / 0.5, 0, 1);
|
|
2079
|
+
return count - 1 - index + (2 * index - count + 1) * t * t * (3 - 2 * t);
|
|
2080
|
+
}
|
|
2040
2081
|
var SpreadEngine = class {
|
|
2041
2082
|
group = new THREE12.Group();
|
|
2042
2083
|
maxPosition;
|
|
@@ -2133,6 +2174,11 @@ var SpreadEngine = class {
|
|
|
2133
2174
|
if (this.press) this.release(new PointerEvent("pointercancel", { pointerId: this.press.id }));
|
|
2134
2175
|
this.wake();
|
|
2135
2176
|
}
|
|
2177
|
+
setShadows(on) {
|
|
2178
|
+
this.settings.lighting.shadows = on;
|
|
2179
|
+
for (const sheet of this.sheets) sheet.mesh.receiveShadow = on;
|
|
2180
|
+
this.wake();
|
|
2181
|
+
}
|
|
2136
2182
|
dispose() {
|
|
2137
2183
|
this.cancelHold();
|
|
2138
2184
|
this.abort.abort();
|
|
@@ -2495,7 +2541,7 @@ var SpreadEngine = class {
|
|
|
2495
2541
|
const depth = i >= this.current ? i - this.current : this.current - 1 - i;
|
|
2496
2542
|
const turning = this.inFlight(i) || this.drag?.index === i;
|
|
2497
2543
|
sheet.mesh.castShadow = this.settings.lighting.shadows && (depth < 3 || turning);
|
|
2498
|
-
layerSheet(sheet.mesh, depth, this.sheets.length, turning);
|
|
2544
|
+
layerSheet(sheet.mesh, depth, this.sheets.length, turning, this.settings.paper.depthBias);
|
|
2499
2545
|
const lift = Math.abs(sheet.uniforms.uFlipRotation.value.y);
|
|
2500
2546
|
if (turning && lift > upright) {
|
|
2501
2547
|
upright = lift;
|
|
@@ -2526,6 +2572,7 @@ var SpreadEngine = class {
|
|
|
2526
2572
|
u.uWrinkleSide.value = -Math.cos(turn);
|
|
2527
2573
|
u.uDirection.value = sheet.directionSmooth;
|
|
2528
2574
|
u.uStackLift.value = sheet.liftBase + sheet.liftSpan * sheet.progress;
|
|
2575
|
+
u.uStackGap.value = this.settings.paper.sheetGap * sheetsUnder(sheet.index, this.sheets.length, sheet.progress);
|
|
2529
2576
|
u.uBendAngle.value = bend;
|
|
2530
2577
|
u.uFold.value.set(cos, sin);
|
|
2531
2578
|
u.uCurl.value.set(curlStart, curlLength);
|
|
@@ -2806,7 +2853,8 @@ var LeafThrough = class extends EventTarget {
|
|
|
2806
2853
|
scene.add(sun, sun.target);
|
|
2807
2854
|
const shading = {
|
|
2808
2855
|
spineContact: { value: new THREE13.Vector3(1, 0, 0) },
|
|
2809
|
-
lightDirection: { value: sun.position.clone().sub(sun.target.position).normalize() }
|
|
2856
|
+
lightDirection: { value: sun.position.clone().sub(sun.target.position).normalize() },
|
|
2857
|
+
origin: { value: new THREE13.Vector3() }
|
|
2810
2858
|
};
|
|
2811
2859
|
let floor2 = null;
|
|
2812
2860
|
if (layout === "spread" && shadows && settings.lighting.floorShadow > 0) {
|
|
@@ -3097,6 +3145,7 @@ export {
|
|
|
3097
3145
|
LIVE_OPTIONS,
|
|
3098
3146
|
preloadRadius,
|
|
3099
3147
|
snapshot,
|
|
3148
|
+
deepEqual,
|
|
3100
3149
|
changedOptions,
|
|
3101
3150
|
sourceKey,
|
|
3102
3151
|
floorPoint,
|
|
@@ -3114,4 +3163,4 @@ export {
|
|
|
3114
3163
|
SpreadEngine,
|
|
3115
3164
|
LeafThrough
|
|
3116
3165
|
};
|
|
3117
|
-
//# sourceMappingURL=chunk-
|
|
3166
|
+
//# sourceMappingURL=chunk-QFOGCGJJ.js.map
|