leafthrough 0.1.4 → 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 +117 -27
- package/dist/LeafThroughObject.d.ts +13 -4
- 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
|
@@ -6,7 +6,8 @@ Vue, Svelte, and a Web Component for everything else (Angular, Solid, Lit, plain
|
|
|
6
6
|
can also be [one object in your own three.js scene](#in-your-own-threejs-scene).
|
|
7
7
|
|
|
8
8
|
**[Live demo](https://dmtrbrl.github.io/leafthrough/)**, with one page per binding and a dev panel
|
|
9
|
-
for every option, method and event
|
|
9
|
+
for every option, method and event, and
|
|
10
|
+
[a three.js scene](https://dmtrbrl.github.io/leafthrough/three.html) with the magazine in it.
|
|
10
11
|
|
|
11
12
|
- **Two layouts.** `spread` is an open magazine with sheets turning over the spine.
|
|
12
13
|
`single` shows one page at a time, and read pages roll up behind the magazine. `auto`
|
|
@@ -139,19 +140,21 @@ error trackers listen. To register the element under another tag, call
|
|
|
139
140
|
|
|
140
141
|
## In your own three.js scene
|
|
141
142
|
|
|
142
|
-
`LeafThroughObject` is the magazine as a `THREE.Group`, for a scene you already have
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
`
|
|
143
|
+
`LeafThroughObject` is the magazine as a `THREE.Group`, for a scene you already have. It
|
|
144
|
+
draws with your renderer and takes pointer input through your camera, so it turns wherever
|
|
145
|
+
it lies, however it's moved, turned or scaled. Every option, method, property and event of
|
|
146
|
+
`LeafThrough` works the same, apart from those about its own canvas and DOM: the options
|
|
147
|
+
`bleed`, `maxPixelRatio`, `plain`, `label` and `formatPageLabel`, and the properties
|
|
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)).
|
|
148
151
|
|
|
149
152
|
```ts
|
|
150
153
|
import { LeafThroughObject } from 'leafthrough'
|
|
151
154
|
|
|
152
|
-
const magazine = new LeafThroughObject({ renderer, camera }, { pages, layout: 'spread'
|
|
155
|
+
const magazine = new LeafThroughObject({ renderer, camera }, { pages, layout: 'spread' })
|
|
153
156
|
magazine.rotation.x = -Math.PI / 2 // lay it on the table
|
|
154
|
-
magazine.scale.setScalar(0.
|
|
157
|
+
magazine.scale.setScalar(0.21) // a page 21 cm wide, in a scene measured in metres
|
|
155
158
|
scene.add(magazine)
|
|
156
159
|
|
|
157
160
|
renderer.setAnimationLoop((now) => {
|
|
@@ -161,38 +164,116 @@ renderer.setAnimationLoop((now) => {
|
|
|
161
164
|
```
|
|
162
165
|
|
|
163
166
|
- **Units.** A page is 1 wide and `pageAspect` tall. The spine runs up the y axis at
|
|
164
|
-
x = 0 and the pages lie on the z = 0 plane, facing +z. A closed
|
|
165
|
-
at x 0 to 1
|
|
166
|
-
|
|
167
|
+
x = 0 and the pages lie on the z = 0 plane, facing +z. A spread closed on its front
|
|
168
|
+
cover shows it at x 0 to 1, and closed on its back cover at x -1 to 0.
|
|
169
|
+
`magazine.bounds.pages` is the pages at rest, the rectangle to frame with your camera;
|
|
170
|
+
`magazine.bounds.reach` is everything a turning page sweeps through.
|
|
167
171
|
- **Input.** The magazine listens for pointer events on `renderer.domElement` (or the
|
|
168
|
-
`domElement` you pass
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
`domElement` you pass, which must show exactly the camera's view). It takes presses in
|
|
173
|
+
its hit area: the pages in `spread` (only the cover's side while closed), and in
|
|
174
|
+
`single` a box round the page with a strip left of the spine for turning back. It
|
|
175
|
+
doesn't look at your scene, so objects in front of the magazine don't block it.
|
|
176
|
+
`magazine.covers(clientX, clientY)` tells you whether it takes a press there; check that
|
|
177
|
+
in your own handlers to leave such presses alone. Hide the magazine (`visible = false`,
|
|
178
|
+
on it or a parent) and it takes none. With `interaction.keyboard: true` the keys work
|
|
179
|
+
while that element has focus; a canvas only takes focus with a `tabIndex`, so give it
|
|
180
|
+
one or use `'global'`. Drags scale with the magazine's size on screen, so they feel the
|
|
181
|
+
same near or far. For touch drags, give the element `touch-action: pan-y pinch-zoom`
|
|
182
|
+
(or `none`), so the browser doesn't take them for scrolling.
|
|
173
183
|
- **Light.** The paper is a `MeshStandardMaterial`, lit by your scene's lights,
|
|
174
|
-
environment, fog and tone mapping. Of the `lighting` options, `
|
|
175
|
-
|
|
176
|
-
|
|
184
|
+
environment, fog and tone mapping. Of the `lighting` options, `shadows` makes the
|
|
185
|
+
sheets cast and receive shadows when your renderer draws them, and `sun.position`
|
|
186
|
+
should point towards your shadow-casting directional light, in the magazine's own
|
|
187
|
+
frame (only the direction counts). It places the shadow a turning sheet casts right by
|
|
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.
|
|
177
193
|
- **Rendering on demand.** If you only draw when something changes, listen for `change`:
|
|
178
194
|
it fires when input, loading or a method call needs a frame. Then keep drawing while
|
|
179
195
|
`tick()` returns true.
|
|
196
|
+
- **Accessibility is yours.** With no DOM of its own, it has no live region, no page
|
|
197
|
+
descriptions and no plain view: pages' `alt` and `text` aren't read. Describe the pages
|
|
198
|
+
in your own page, and announce `pagechange` if readers should hear it.
|
|
180
199
|
|
|
181
200
|
```ts
|
|
182
201
|
magazine.tick(now) // advance; true while the pages still move
|
|
183
|
-
magazine.covers(e.clientX, e.clientY) // whether a press there
|
|
202
|
+
magazine.covers(e.clientX, e.clientY) // whether the magazine takes a press there
|
|
184
203
|
magazine.bounds // { pages, reach }, in the magazine's own units
|
|
185
204
|
magazine.camera = otherCamera // cast input from another camera
|
|
186
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
|
|
187
207
|
magazine.addEventListener('change', () => requestRender())
|
|
188
208
|
magazine.addEventListener('pagechange', (e) => e.detail) // also ready, pageload, error
|
|
189
209
|
magazine.dispose() // free everything and leave the scene
|
|
190
210
|
```
|
|
191
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
|
+
|
|
192
272
|
The [three.js demo](https://dmtrbrl.github.io/leafthrough/three.html) takes a magazine
|
|
193
273
|
out of a portrait paperboard mailer on a table; its source is
|
|
194
|
-
[`demo/main-three.ts`](demo/main-three.ts),
|
|
195
|
-
|
|
274
|
+
[`demo/main-three.ts`](https://github.com/dmtrbrl/leafthrough/blob/main/demo/main-three.ts),
|
|
275
|
+
with the native three.js mailer geometry in
|
|
276
|
+
[`demo/envelope.ts`](https://github.com/dmtrbrl/leafthrough/blob/main/demo/envelope.ts).
|
|
196
277
|
|
|
197
278
|
## Sizing
|
|
198
279
|
|
|
@@ -247,6 +328,8 @@ The magazine fills the element you give it and keeps its proportions, centred.
|
|
|
247
328
|
| `wrinkles` | `1` | Waviness of resting sheets; `0` lays them flat. |
|
|
248
329
|
| `bulge` | `1` | How much pages rise off the spine. |
|
|
249
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). |
|
|
250
333
|
|
|
251
334
|
**`lighting`**: `ambient: { sky, ground, intensity }`, `sun: { color, intensity, position }`,
|
|
252
335
|
`shadows` (default `true`), `shadowMapSize` (default `2048`), and `floorShadow`. That last
|
|
@@ -297,7 +380,8 @@ magazine.addEventListener('error', (e) => e.detail) // { error, page }
|
|
|
297
380
|
|
|
298
381
|
Both compare options by value. Changing callbacks or the label is free. Changing `pages`,
|
|
299
382
|
`layout`, paper, lighting, motion or interaction rebuilds the scene (a few milliseconds)
|
|
300
|
-
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()`
|
|
301
385
|
after that. The React, Vue and Svelte bindings and the element use `setOptions()`, so
|
|
302
386
|
removing a prop or attribute restores its default. Options are copied on every call, so an
|
|
303
387
|
object or array changed in place and passed again (as Vue's reactive props are) counts as
|
|
@@ -352,7 +436,8 @@ off screen or the tab is hidden.
|
|
|
352
436
|
The 3D canvas is hidden from screen readers, so give pages an `alt` and their `text` (see
|
|
353
437
|
[Pages](#pages)). The magazine keeps the visible pages' descriptions and text in the page,
|
|
354
438
|
visually hidden, where screen readers can read them; each page is a `figure` named by
|
|
355
|
-
`formatPageLabel`.
|
|
439
|
+
`formatPageLabel`. `LeafThroughObject` has no DOM, so none of this comes with it (see
|
|
440
|
+
[In your own three.js scene](#in-your-own-threejs-scene)).
|
|
356
441
|
|
|
357
442
|
**Plain view.** `plain: true` shows the pages as flat images, with the same layouts,
|
|
358
443
|
navigation, keyboard and events, and no 3D. Clicking the right half turns forward, the left
|
|
@@ -387,7 +472,10 @@ How it works:
|
|
|
387
472
|
- `content.ts` keeps the visible pages' content as DOM: hidden for screen readers in 3D,
|
|
388
473
|
the pages themselves in the plain view.
|
|
389
474
|
- `textures.ts` loads, prioritises and unloads page textures.
|
|
390
|
-
- `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.
|
|
391
479
|
- `LeafThrough.ts` handles the DOM, sizing, loading, layout switching, keyboard and
|
|
392
480
|
events. `LeafThroughObject.ts` does the same inside someone else's scene, without the DOM.
|
|
393
481
|
- Engines take input through `EngineInput` (`engine.ts`): an element to listen on, a ray
|
|
@@ -395,6 +483,8 @@ How it works:
|
|
|
395
483
|
their own floor plane, so the same code serves the magazine's own camera and any scene's.
|
|
396
484
|
- `navigation.ts` maps pages to engine positions, riffles an animated `goTo()` and binds
|
|
397
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.
|
|
398
488
|
|
|
399
489
|
## Credits
|
|
400
490
|
|
|
@@ -403,4 +493,4 @@ The page deformation and interaction model are reverse-engineered from the magaz
|
|
|
403
493
|
|
|
404
494
|
## License
|
|
405
495
|
|
|
406
|
-
[MIT](LICENSE)
|
|
496
|
+
[MIT](https://github.com/dmtrbrl/leafthrough/blob/main/LICENSE)
|
|
@@ -1,13 +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
|
-
* `
|
|
8
|
-
*
|
|
7
|
+
* `shadows` (whether sheets cast and receive them) and `sun.position` apply: the lights are
|
|
8
|
+
* your scene's. Point `sun.position` towards your shadow-casting directional light, in the
|
|
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.
|
|
9
14
|
*/
|
|
10
|
-
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
|
+
};
|
|
11
19
|
/** What the magazine draws with, from your scene. */
|
|
12
20
|
export interface LeafThroughObjectContext {
|
|
13
21
|
/** The renderer drawing your scene. Pages load into it. */
|
|
@@ -137,6 +145,7 @@ export declare class LeafThroughObject extends THREE.Group<LeafThroughObjectEven
|
|
|
137
145
|
private pickLayout;
|
|
138
146
|
private watchSpreadQuery;
|
|
139
147
|
private remount;
|
|
148
|
+
private resolve;
|
|
140
149
|
private readonly axis;
|
|
141
150
|
private pageAxis;
|
|
142
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
|